Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lambdas in the classical Operation enum example

As many of you may know, there is a classical example of the Operation enum (using Java 8 standard interface now though), that is the following:

enum Operation implements DoubleBinaryOperator {
    PLUS("+") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left + right;
        }
    },
    MINUS("-") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left - right;
        }
    },
    MULTIPLY("*") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left * right;
        }
    },
    DIVIDE("/") {
        @Override
        public double applyAsDouble(final double left, final double right) {
            return left / right;
        }
    };

    private final String symbol;

    private Operation(final String symbol) {
        this.symbol = symbol;
    }

    public String getSymbol() {
        return symbol;
    }
}

Tested with:

Arrays.stream(Operation.values())
        .forEach(op -> System.out.println("Performing operation " + op.getSymbol() + " on 2 and 4: " + op.applyAsDouble(2, 4)));

It provides:

Performing operation + on 2 and 4: 6.0
Performing operation - on 2 and 4: -2.0
Performing operation * on 2 and 4: 8.0
Performing operation / on 2 and 4: 0.5

But I feel like we can do better with Java 8, hence I implemented the following:

enum Operation implements DoubleBinaryOperator {
    PLUS    ("+", (l, r) -> l + r),
    MINUS   ("-", (l, r) -> l - r),
    MULTIPLY("*", (l, r) -> l * r),
    DIVIDE  ("/", (l, r) -> l / r);

    private final String symbol;
    private final DoubleBinaryOperator binaryOperator;

    private Operation(final String symbol, final DoubleBinaryOperator binaryOperator) {
        this.symbol = symbol;
        this.binaryOperator = binaryOperator;
    }

    public String getSymbol() {
        return symbol;
    }

    @Override
    public double applyAsDouble(final double left, final double right) {
        return binaryOperator.applyAsDouble(left, right);
    }
}

Functionally it is equivalent, however are both implementations still similar, or are there some hidden details that make the new version worse as the old version?

And lastly, is the lambda way the preferred way to do it as of Java 8?

like image 563
skiwi Avatar asked Apr 29 '14 09:04

skiwi


People also ask

What is a lambda expression example?

Lambda expressions basically express instances of functional interfaces (An interface with single abstract method is called functional interface. An example is java.lang.Runnable). lambda expressions implement the only abstract function and therefore implement functional interfaces.

What is enum with example?

An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week.

Why are lambdas expression used in Java 8?

Lambda expression is a new and important feature of Java which was included in Java SE 8. It provides a clear and concise way to represent one method interface using an expression. It is very useful in collection library. It helps to iterate, filter and extract data from collection.

How are lambdas implemented?

A lambda works like this: Generates invokedynamic call site and uses a lambdafactory to return the functional implementation. Lambda converted to a method to be invoked by invokedynamic. The method is stored in a class as a private static method.


4 Answers

Obviously, the lambda version is far more readable. Not only is it shorter, it allows a reader to see the the implementation operator at the first glance in the constructor. Imagine you want to extend the enum to support int calculation as well…

From a performance point of view you are exchanging the anonymous enum inner classes by generated lambda classes. The lambda version adds another level of delegation but that’s no challenge to the HotSpot optimizer. It’s unlikely to see any difference regarding the execution performance.

However, when applying the lambda pattern consequently you might get a speedup of the startup of applications using the class. The reason is that for the traditional specialized enum approach the Java compiler has to generate an inner class for each case which resides either in the file system or (possibly zip-compressed) in a Jar file. Generating the byte code for a lambda class (which has a very simple structure) on the fly is usually faster than loading a class. It might help as well that there is no access checking for the generated lambda classes.

To summarize it:

  • The lambda approach is easier to read and its code is more maintainable (the big point)
  • The execution performance is roughly the same
  • The startup time might be shorter for the lambda approach

So it’s a big win for the lambda. Yes, I think the lambda way the preferred way to do it as of Java 8.

like image 76
Holger Avatar answered Oct 13 '22 01:10

Holger


It depends how you define better.

In your case and in my opinion, the lambdas are a pure win. You can even reuse some existing JDK functions, e.g.:

enum Operation implements DoubleBinaryOperator {
    PLUS    ("+", Double::sum),
    ...
}

This is short and readable. I don't think anything reasonable can be said about performance w/o benchmarking your code.

Lambdas are implemented with invokeDynamic to dynamically link call site to actual code to execute; no anonymous, inner classes.

like image 37
emesx Avatar answered Oct 13 '22 01:10

emesx


I'm surprised no one mentioned this, but the lambda approach can get hairy when implementing multiple methods. Passing a bunch of nameless lambdas into a constructor will be more concise, but not necessarily more readable.

Also, the benefit of using a lambda decreases as the function grows in size. If your lambda is more than several lines long, an override might be just as easy to read, if not easier.

like image 34
shmosel Avatar answered Oct 13 '22 01:10

shmosel


Define worse, most likely it uses a little more byte code and is slightly slower.

Unless these are important to you, I would use the approach you find clearer and simpler.

like image 23
Peter Lawrey Avatar answered Oct 12 '22 23:10

Peter Lawrey