You’ve heard that Java 8 now has the main prerequisite for functional programming: functions. But you’re too busy writing Scala code to dig into the details, so we’ve put together a crib sheet on what’s in Java 8 from a Scala developer perspective.
The syntax for lambda expressions uses a thin arrow:
(arg1, arg2) -> expression.
The compiler will infer a matching interface representation of the lambda expression. This comes from the target type where the lambda expression is being used. For example, a method that requires
Comparator<Integer>as an argument can accept an expression of
(Integer a, Integer b) -> a-b. The compiler will infer that
(a,b) -> a-bis a valid representation of
Comparator<Integer>because what it means to be a
Comparator<T>is to take two values and return an integer.
There is a misconception that lambda expressions are just an alternative syntax for anonymous classes. This is not true. The compiler generates a invoke dynamic byte code for lambda expressions to defer the instantiation of the target interface to run time. This means the runtime can decide how to best support the expressions, which could be anonymous classes, method handles, or some other mechanism.
The concept of a functional interface is an interface that has only one abstract method.
The type inferencing in Java 8 allows a lambda expression to be used where a functional interface is required. In that last example,
Comparator<T>is a functional interface because is has one abstract method:
int compare(T o1, T o2).
This means existing code and libraries, created before Java 8, can accept lambda expressions as arguments if those arguments happen to be functional interfaces.
Another example of a functional interface is
java.util.functionintroduces general functional interfaces, including the one argument
Function<T,R>and the two argument
BiFunction<T,U,R>. For example:
Function<Integer,String> myToString = i -> i.toString();. These functions are invoked by calling
There are a small number of additional functional interfaces such as
Predicate<T>, although there are a variety of ways of invoking these functions:
accept, depending on the interface.
To represent something like a Unit argument or return type, two functional interfaces have been included:
If you need more than two arguments, write your own functional interface and the type inferencer will work just fine on an expression like
(a,b,c) -> r.
Yes, use higher-order functions. Your
t ⇒ r ⇒ ris a
Function<T, Function<U,R>>, represented as
t -> u -> r.
For performance there are specialised version of functional interfaces for primitive types in the package. There is no
Streams and Collections
The Java collections API has been enhanced to include support for lambda expressions.
However, the new Stream API is much closer to the collections library in Scala. It supports the usual
flatMapoperations you know and love.
The key concept is that a Stream builds up a pipeline of (lazy) operations, which are executed when you call a terminal operator on the stream. You’ll know a terminal operator when you see it, because it doesn’t return another
Stream<T>. Examples are:
That’s the crib sheet. There’s no analog to implicit, by-name parameters, case classes, pattern matching, or many other features you now expect. But Oracle have done a great job getting functions into Java in a Java-like way.
I recorded my screen during a live coding presentation, so if you want to spend more time seeing how the Java 8 features work out for functional programming, hit the play button:
You can also grab the source code that goes with the recording.