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.
Lambda expressions
-
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-b
is a valid representation ofComparator<Integer>
because what it means to be aComparator<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.
Functional Interfaces
-
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
Runnable
.
JUF
-
The package
java.util.function
introduces general functional interfaces, including the one argumentFunction<T,R>
and the two argumentBiFunction<T,U,R>
. For example:Function<Integer,String> myToString = i -> i.toString();
. These functions are invoked by callingapply
. -
There are a small number of additional functional interfaces such as
Predicate<T>
, although there are a variety of ways of invoking these functions:apply
,get
,test
,accept
, depending on the interface. -
To represent something like a Unit argument or return type, two functional interfaces have been included:
Consumer<T>
andSupplier<T>
. -
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 ⇒ r
is aFunction<T, Function<U,R>>
, represented ast -> u -> r
. -
For performance there are specialised version of functional interfaces for primitive types in the package. There is no
@specialized
annotation.
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
map
,filter
,reduce
,flatMap
operations 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:count
,forEach
.
Optional
-
Optional<T>
is yourOption[T]
. -
Optional.ofNullable
is yourOption.apply
.
Got it?
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.