I started using Java professionally when it was already at version 8. I anticipated Java to be lot more fun compared to when I learned it first sometime in early 2000s. With lambdas, the Streams API, and completable futures Java now has a handful of functional goodies. Coming from C++11 world, I did not want to give up on basic language capabilities such as lambdas. Java 8 seemed like a good point to enter.
Alas, my excitement did not last for too long. Using lambdas in Java is like scratching fingernails on a chalkboard. Here’s why.
Checked exceptions make verbose lambdas
Consider an example of mapping over a list of CompletableFutures. Suppose, you want to double the value enclosed in every future in a list. Here’s what you have to write today.
List<CompletableFuture<Long>> list = Arrays.asList(CompletableFuture.completedFuture(10L)); list.stream() .map(f -> { try { return 2*f.get(); } catch(InterruptedException | ExecutionException e) { // return -1; // Return -1 for error? Yuck! throw new RuntimeException(e); // Sigh! } });
It’s verbose beyond my wildest imagination. The code should have been either 1) .map(CompletableFuture::get)
or 2) .map(f -> f.get())
Neither of them work because the instance method CompletableFuture::get
might throw checked InterruptedException
and ExecutionException
. The functional interface that map relies on is Function
. It has an apply
method that is not suitable for any lambda or method reference that might throw a checked exception.
People have invented many tricks to work around this problem but they are all crutches and don’t cut down the verbosity to the cleanest possible code.
One of the possible techniques is to create wrappers that catch a generic exception and take generic actions like logging them.
Others have suggested a wrapper to simply convert the checked exception into a RuntimeException
and let it escape.
A more fancy technique is to pass exception types as generic type parameters and throw checked exceptions without declaring them. That’s sneaky but also not very useful as it works only for local throw statements.
For example, method throwAsUnchecked
below is fine as long as there’s only a local throw statement.
import java.io.IOException; public class prog { Exception e = new IOException(&amp;amp;quot;ignore me&amp;amp;quot;); public static void main(String[] args) { prog p = new prog(); p.iShouldDeclareACheckedExceptionButIDont(); } private void iShouldDeclareACheckedExceptionButIDont() { throwAsUnchecked(); } @SuppressWarnings (&amp;amp;quot;unchecked&amp;amp;quot;) private &amp;amp;lt;E extends Throwable&amp;amp;gt; void throwAsUnchecked() throws E { iMayThrowIOException(); // compiler error throw (E)e; } void iMayThrowIOException() throws IOException {} }
The only reasonable and the right technique is to create checked exception friendly SAM (Single Abstract Method) interfaces that are parallel to the already existing standard SAM interfaces. Of course, it causes an explosion in the number of SAM interfaces, which is already bloated due to primitive specializations.
No Immediately Invoked Function Expressions (IIFE pattern) in Java
Most languages I’ve used in the past, IIFE pattern is quite convenient. It’s famous in Javascript. I’ll take a Lua example, which is nearly identical.
local y = (function(x) return 2*x end)(5) print (y) -- prints 10
The idea is to create an anonymous function and call it right-away. This pattern comes in handy when there are multiple steps needed to initialize a value. Instead of creating a separate named function and passing (possibly many) arguments to it, IIFE carries you through the day. I like IIFE because it avoids pollution of the global namespace in scripting languages.
It’s not limited to just scripting languages. C++ supports it too; quite elegantly.
int y = [](int x){ return 2*x; }(5); std::cout << y; // prints 10
Here’s a fun fact.
The smallest IIFE expression in C++ is just
[]{}()
On the other hand, Java’s equivalent of IIFE makes my cry.
Long y = ((Function<Long,Long>)(x -> 2*x)).apply(5L);
For one, the lambda expression must be casted to the functional interface you care about. You have to spell out all the parameter types. It’s not convenient at all when you have long types. Last but not the least, you have to remember the SAM in the functional interface and use it to invoke the lambda. It’s the last part that really gets me.
I went on and created a list of all the SAM methods in the Java 8 SDK. There are dozens of names, literally. Here’s a small list for some (bad) taste: Function::apply, Supplier::get
Predicate::test, Consumer::accept, BiConsumer::accept, BiFunction::apply, BiPredicate::test, BooleanSupplier::getAsBoolean, ToDoubleFunction::applyAsDouble, Runnable::run, Comparator::compare
To be fair, C# IIFE is not the best of it’s kind anyhow. It definitely feels more function like than that of Java’s.
int v = new Func<int, int>((x) => { return 2*x; })(5);
OK, so there you have it. My pet peeves about Java 8 lambdas.