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?
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.
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.
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.
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.
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:
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With