The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.
Java was designed in the 1990s as an object-oriented programming language, when object-oriented programming was the principal paradigm for software development. Long before there was object-oriented programming, there were functional programming languages such as Lisp and Scheme, but their benefits were not much appreciated outside academic circles. Recently, functional programming has risen in importance because it is well suited for concurrent and event-driven (or "reactive") programming. That doesn't mean that object orientation is bad. Instead, the winning strategy is to blend object-oriented and functional programming. This makes sense even if you are not interested in concurrency. For example, collection libraries can be given powerful APIs if the language has a convenient syntax for functional expressions.
The principal enhancement in Java 8 is the addition of functional programming constructs to its object-oriented roots. In this article, I demonstrate the basic syntax and examine how to use it several important contexts. The key points are:
<!--[if !supportLists]-->· <!--[endif]-->A lambda expression is a block of code with parameters.
<!--[if !supportLists]-->· <!--[endif]-->Use a lambda expression whenever you want a block of code executed at a later point in time.
<!--[if !supportLists]-->· <!--[endif]-->Lambda expressions can be converted to functional interfaces.
<!--[if !supportLists]-->· <!--[endif]-->Lambda expressions can access effectively final variables from the enclosing scope.
<!--[if !supportLists]-->· <!--[endif]-->Method and constructor references refer to methods or constructors without invoking them.
<!--[if !supportLists]-->· <!--[endif]-->You can now add default and static methods to interfaces that provide concrete implementations.
<!--[if !supportLists]-->· <!--[endif]-->You must resolve any conflicts between default methods from multiple interfaces.
Why Lambdas?
A lambda expression is a block of code that you can pass around so it can be executed later, just once or multiple times. Before getting into the syntax (or even the curious name), let's step back and see where you have used similar code blocks in Java all along.
When you want to do work in a separate thread, you put the work into the run
method of a Runnable
, like this:
1 2 3 4 5 6 7 |
|
Then, when you want to execute this code, you construct an instance of the Worker
class. You can then submit the instance to a thread pool, or keep it simple and start a new thread:
1 2 |
|
The key point is that the run
method contains code that you want to execute in a separate thread.
Consider sorting with a custom comparator. If you want to sort strings by length instead of the default dictionary order, you can pass a Comparator
object to the sort
method:
1 2 3 4 5 6 7 |
|
The sort
method keeps calling the compare
method, rearranging the elements if they are out of order, until the array is sorted. You give the sort
method a snippet of code needed to compare elements, and that code is integrated into the rest of the sorting logic, which you'd probably not care to reimplement. Note that the call Integer.compare(x, y)
returns zero if x
and y
are equal, a negative number if x < y
, and a positive number if x > y
. This static method was added to Java 7. You shouldn't compute x - y
to compare x
and y
because that computation can overflow for large operands of opposite sign.
As another example for deferred execution, consider a button callback. You put the callback action into a method of a class implementing the listener interface, construct an instance, and register the instance with the button. That happens so often that many programmers use the "anonymous instance of anonymous class" syntax:
1 2 3 4 5 |
|
What matters is the code inside the handle
method. That code is executed whenever the button is clicked.
Since Java 8 positions JavaFX as the successor to the Swing GUI toolkit, I use JavaFX in these examples. (See Eric Bruno's posts for more information on JavaFX. — Ed.) The details don't matter. In every user interface toolkit, be it Swing, JavaFX, or Android, you give a button some code that you want to run when the button is clicked.
In all three examples, you saw the same approach. A block of code was passed to someone — a thread pool, a sort
method, or a button. The code was called at some later time.
Up to now, giving someone a block of code hasn't been easy in Java. You couldn't just pass code blocks around. Java is an object-oriented language, so you had to construct an object belonging to a class that has a method with the desired code.
In other languages, it is possible to work with blocks of code directly. The Java designers have resisted adding this feature for a long time. After all, a great strength of Java is its simplicity and consistency. A language can become an unmaintainable mess if it includes every feature that yields marginally more-concise code. However, in those other languages, it isn't just easier to spawn a thread or to register a button-click handler; large swaths of their APIs are simpler, more consistent, and more powerful. In Java, one could have written similar APIs that take objects of classes implementing a particular function, but such APIs would be unpleasant to use.
For some time now, the question was not whether to augment Java for functional programming, but how to do it. It took several years of experimentation before a design emerged that is a good fit for Java. In the next section, you will see how you can work with blocks of code in Java 8.
The Syntax of Lambda Expressions
Consider the previous sorting example again. We pass code that checks whether one string is shorter than another. We compute
1 |
|
What are first and second? They are both strings! Java is a strongly typed language, and we must specify that as well:
1 2 |
|
You have just seen your first lambda expression! Such an expression is simply a block of code, together with the specification of any variables that must be passed to the code.
Why the name? Many years ago, before there were any computers, the logician Alonzo Church wanted to formalize what it means for a mathematical function to be effectively computable. (Curiously, there are functions that are known to exist, but nobody knows how to compute their values.) He used the Greek letter lambda (λ) to mark parameters. Had he known about the Java API, he would have written:
1 |
|
Why the letter λ? Did Church run out of other letters of the alphabet? Actually, the venerablePrincipia Mathematica used the ˆ accent to denote free variables, which inspired Church to use an uppercase lambda (Λ) for parameters. But in the end, he switched to the lowercase version. Ever since, an expression with parameter variables has been called a "lambda expression."
The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.
You have just seen one form of lambda expressions in Java: parameters, the -> arrow, and an expression. If the code carries out a computation that doesn't fit in a single expression, write it exactly like you would have written a method: enclosed in {}
and with explicit return
statements. For example,
1 2 3 4 5 |
|
If a lambda expression has no parameters, you still supply empty parentheses, just as with a parameterless method:
1 |
|
If the parameter types of a lambda expression can be inferred, you can omit them. For example,
1 2 3 |
|
Here, the compiler can deduce that first
and second
must be strings because the lambda expression is assigned to a string comparator. (We will have a closer look at this assignment later.)
If a method has a single parameter with inferred type, you can even omit the parentheses:
1 2 3 |
|
You can add annotations or the final
modifier to lambda parameters in the same way as for method parameters:
1 2 |
|
You never specify the result
type of a lambda expression. It is always inferred from context. For example, the expression
1 |
|
can be used in a context where a result of type int
is expected.
Note that it is illegal for a lambda expression to return a value in some branches but not in others. For example, (int x) -> { if (x >= 0) return 1; }
is invalid.
Functional Interfaces
As we discussed, there are many existing interfaces in Java that encapsulate blocks of code, such as Runnable
or Comparator
. Lambdas are backwards compatible with these interfaces.
You can supply a lambda expression whenever an object of an interface with a single abstract method is expected. Such an interface is called a functional interface.
You may wonder why a functional interface must have a single abstract method. Aren't all methods in an interface abstract? Actually, it has always been possible for an interface to redeclare methods from the Object
class such as toString
or clone
, and these declarations do not make the methods abstract. (Some interfaces in the Java API redeclare Object
methods in order to attach javadoc comments. Check out the Comparator API for an example.) More importantly, as you will see shortly, in Java 8, interfaces can declare non-abstract methods.
To demonstrate the conversion to a functional interface, consider the Arrays.sort
method. Its second parameter requires an instance of Comparator
, an interface with a single method. Simply supply a lambda:
1 2 |
|
Behind the scenes, the Arrays.sort
method receives an object of some class that implementsComparator<String>
. Invoking the compare
method on that object executes the body of the lambda expression. The management of these objects and classes is completely implementation dependent, and it can be much more efficient than using traditional inner classes. It is best to think of a lambda expression as a function, not an object, and to accept that it can be passed to a functional interface.
This conversion to interfaces is what makes lambda expressions so compelling. The syntax is short and simple. Here is another example:
1 2 |
|
That's awfully easy to read.
In fact, conversion to a functional interface is the only thing that you can do with a lambda expression in Java. In other programming languages that support function literals, you can declare function types such as (String, String) -> int
, declare variables of those types, and use the variables to save function expressions. In Java, you can't even assign a lambda expression to a variable of type Object
because Object
is not a functional interface. The Java designers decided to stick strictly with the familiar concept of interfaces instead of adding function types to the language.
The Java API defines several generic functional interfaces in the java.util.function
package. One of the interfaces, BiFunction<T, U, R>
, describes functions with parameter types T
and U
and return type R
. You can save our string comparison lambda in a variable of that type:
1 2 |
|
However, that does not help you with sorting. There is no Arrays.sort
method that wants aBiFunction
. If you have used a functional programming language before, you may find this curious. But for Java programmers, it's pretty natural. An interface such as Comparator
has a specific purpose, not just a method with given parameter and return types. Java 8 retains this flavor. When you want to do something with lambda expressions, you still want to keep the purpose of the expression in mind, and have a specific functional interface for it.
The interfaces in java.util.function
are used in several Java 8 APIs, and you will likely see them elsewhere in the future. But keep in mind that you can equally well convert a lambda expression into a functional interface that is a part of whatever API you use today. Also, you can tag any functional interface with the @FunctionalInterface
annotation. This has two advantages. The compiler checks that the annotated entity is an interface with a single abstract method. And the javadoc page includes a statement that your interface is a functional interface. You are not required to use the annotation. Any interface with a single abstract method is, by definition, a functional interface. But using the @FunctionalInterface
annotation is a good idea.
Finally, note that checked exceptions matter when a lambda is converted to an instance of a functional interface. If the body of a lambda expression can throw a checked exception, that exception needs to be declared in the abstract method of the target interface. For example, the following would be an error:
1 2 |
|
Because the Runnable.run
cannot throw any exception, this assignment is illegal. To fix the error, you have two choices. You can catch the exception in the body of the lambda expression. Or you can assign the lambda to an interface whose single abstract method can throw the exception. For example, the call
method of the Callable
interface can throw any exception. Therefore, you can assign the lambda to a Callable<Void>
(if you add a statement return null
).
Method References
Sometimes, there is already a method that carries out exactly the action that you'd like to pass on to some other code. For example, suppose you simply want to print the event
object whenever a button is clicked. Of course, you could call
1 |
|
It would be nicer if you could just pass the println
method to the setOnAction
method. Here is how you do that:
1 |
|
The expression System.out::println
is a method reference that is equivalent to the lambda expression x -> System.out.println(x
).
As another example, suppose you want to sort strings regardless of letter case. You can pass this method expression:
1 |
|
As you can see from these examples, the ::
operator separates the method name from the name of an object or class. There are three principal cases:
<!--[if !supportLists]-->· <!--[endif]-->object::instanceMethod
<!--[if !supportLists]-->· <!--[endif]-->Class::staticMethod
<!--[if !supportLists]-->· <!--[endif]-->Class::instanceMethod
In the first two cases, the method reference is equivalent to a lambda expression that supplies the parameters of the method. As already mentioned, System.out::println
is equivalent to x -> System.out.println(x)
. Similarly, Math::pow
is equivalent to (x, y) -> Math.pow(x, y)
. In the third case, the first parameter becomes the target of the method. For example,String::compareToIgnoreCase
is the same as (x, y) -> x.compareToIgnoreCase(y)
.
When there are multiple overloaded methods with the same name, the compiler will try to find from the context which one you mean. For example, there are two versions of the Math.maxmethod, one for integers and one for double values. Which one gets picked depends on the method parameters of the functional interface to which Math::max is converted. Just like lambda expressions, method references don't live in isolation. They are always turned into instances of functional interfaces.
You can capture the this parameter in a method reference. For example, this::equals is the same as x -> this.equals(x). It is also valid to use super. The method expressionsuper::instanceMethod uses this as the target and invokes the superclass version of the given method. Here is an artificial example that shows the mechanics:
1 2 3 4 5 6 7 8 9 10 11 12 |
classGreeter { publicvoidgreet() { System.out.println("Hello, world!"); } }
classConcurrentGreeter extendsGreeter { publicvoidgreet() { Thread t = newThread(super::greet); t.start(); } } |
When the thread starts, its Runnable is invoked, and super::greet is executed, calling the greetmethod of the superclass. (Note that in an inner class, you can capture the this reference of an enclosing class as EnclosingClass.this::method or EnclosingClass.super::method.)
Constructor References
Constructor references are just like method references, except that the name of the method isnew. For example, Button::new is a reference to a Button constructor. Which constructor? It depends on the context. Suppose you have a list of strings. Then, you can turn it into an array of buttons, by calling the constructor on each of the strings, with the following invocation:
1 2 3 |
List<String> labels = ...; Stream<Button> stream = labels.stream().map(Button::new); List<Button> buttons = stream.collect(Collectors.toList()); |
Details of the stream, map, and collect methods are beyond the scope of this article. For now, what's important is that the map method calls the Button(String) constructor for each listelement. There are multiple Buttonconstructors, but the compiler picks the one with a Stringparameter because it infers from the context that the constructor is called with a string.
You can form constructor references with array types. For example, int[]::new is a constructor reference with one parameter: the length of the array. It is equivalent to the lambda expressionx -> new int[x].
Array constructor references are useful to overcome a limitation of Java. It is not possible to construct an array of a generic type T. The expression new T[n] is an error since it would be erased to new Object[n]. That is a problem for library authors. For example, suppose we want to have an array of buttons. The Stream interface has a toArray method that returns an Objectarray:
1 |
Object[] buttons = stream.toArray(); |
But that is unsatisfactory. The user wants an array of buttons, not objects. The stream library solves that problem with constructor references. Pass Button[]::new to the toArray method:
1 |
Button[] buttons = stream.toArray(Button[]::new); |
The toArray method invokes this constructor to obtain an array of the correct type. Then it fills and returns the array.
The single most important change in Java 8 enables faster, clearer coding and opens the door to functional programming. Here's how it works.
Variable Scope
Often, you want to be able to access variables from an enclosing method or class in a lambda expression. Consider this example:
1 2 3 4 5 6 7 8 9 |
|
Consider a call:
1 |
|
Now look at the variables count
and text
inside the lambda expression. Note that these variables are not defined in the lambda expression. Instead, these are parameter variables of therepeatMessage
method.
If you think about it, something not obvious is going on here. The code of the lambda expression may run long after the call to repeatMessage
has returned and the parameter variables are gone. How do the text
and count
variables stay around?
To understand what is happening, we need to refine our understanding of a lambda expression. A lambda expression has three ingredients:
<!--[if !supportLists]-->1. <!--[endif]-->A block of code
<!--[if !supportLists]-->2. <!--[endif]-->Parameters
<!--[if !supportLists]-->3. <!--[endif]-->Values for the free variables; that is, the variables that are not parameters and not defined inside the code
In our example, the lambda expression has two free variables, text
and count
. The data structure representing the lambda expression must store the values for these variables, in our case, "Hello"
and 1000
. We say that these values have been captured by the lambda expression. (It's an implementation detail how that is done. For example, one can translate a lambda expression into an object with a single method, so that the values of the free variables are copied into instance variables of that object.)
The technical term for a block of code together with the values of the free variables is a closure. If someone gloats that their language has closures, rest assured that Java has them as well. In Java, lambda expressions are closures. In fact, inner classes have been closures all along. Java 8 gives us closures with an attractive syntax.
As you have seen, a lambda expression can capture the value of a variable in the enclosing scope. In Java, to ensure that the captured value is well defined, there is an important restriction. In a lambda expression, you can only reference variables whose value doesn't change. For example, the following is illegal:
1 2 3 4 5 6 7 8 9 10 |
|
There is a reason for this restriction. Mutating variables in a lambda expression is not thread-safe. Consider a sequence of concurrent tasks, each updating a shared counter.
1 2 3 4 |
|
If this code were legal, it would be very, very bad. The increment matches++
is not atomic, and there is no way of knowing what would happen if multiple threads execute that increment concurrently.
Inner classes can also capture values from an enclosing scope. Before Java 8, inner classes were allowed to access only final local variables. This rule has now been relaxed to match that for lambda expressions. An inner class can access any effectively final local variable; that is, any variable whose value does not change.
Don't count on the compiler to catch all concurrent access errors. The prohibition against mutation holds only for local variables. If matches
is an instance or static variable of an enclosing class, then no error is reported, even though the result is just as undefined.
Also, it's perfectly legal to mutate a shared object, even though it is unsound. For example,
1 2 3 4 |
|
Note that the variable matches
is effectively final. (An effectively final variable is a variable that is never assigned a new value after it has been initialized.) In our case, matches
always refers to the same ArrayList
object. However, the object is mutated, and that is not thread-safe. If multiple threads call add
, the result is unpredictable.
There are safe mechanisms for counting and collecting values concurrently. You may want to use streams to collect values with certain properties. In other situations, you may want to use thread-safe counters and collections.
As with inner classes, there is an escape hatch that lets a lambda expression update a counter in an enclosing local scope. Use an array of length 1, like this:
1 2 |
|
Of course, code like this is not thread-safe. For a button callback, that doesn't matter, but in general, you should think twice before using this trick.
The body of a lambda expression has the same scope as a nested block. The same rules for name conflicts and shadowing apply. It is illegal to declare a parameter or a local variable in the lambda that has the same name as a local variable.
1 2 3 4 |
|
Inside a method, you can't have two local variables with the same name. Therefore, you can't introduce such variables in a lambda expression either. When you use the this
keyword in a lambda expression, you refer to the this
parameter of the method that creates the lambda. For example, consider
1 2 3 4 5 6 |
|
The expression this.toString()
calls the toString
method of the Application
object, not theRunnable
instance. There is nothing special about the use of this
in a lambda expression. The scope of the lambda expression is nested inside the doWork
method, and this
has the same meaning anywhere in that method.
Default Methods
Many programming languages integrate function expressions with their collections library. This often leads to code that is shorter and easier to understand than the loop equivalent. For example, consider a loop:
1 2 |
|
There is a better way. The library designers can supply a forEach
method that applies a function to each element. Then you can simply call
1 |
|
That's fine if the collections library has been designed from the ground up. But the Java collections library was designed many years ago, and there is a problem. If the Collection
interface gets new methods, such as forEach
, then every program that defines its own class implementing Collection
will break until it, too, implements that method. That is simply unacceptable in Java.
The Java designers decided to solve this problem once and for all by allowing interface methods with concrete implementations (called default methods). Those methods can be safely added to existing interfaces. In this section, we'll look at default methods in detail. In Java 8, the forEach
method has been added to the Iterable
interface, a superinterface of Collection
, using the mechanism that I will describe here.
Consider this interface:
1 2 3 4 |
|
The interface has two methods: getId
, which is an abstract method, and the default methodgetName
. A concrete class that implements the Person
interface must, of course, provide an implementation of getId
, but it can choose to keep the implementation of getName
or to override it.
Default methods put an end to the classic pattern of providing an interface and an abstract class that implements most or all of its methods, such as Collection/AbstractCollection
orWindowListener/WindowAdapter
. Now, you can just implement the methods in the interface.
What happens if the exact same method is defined as a default method in one interface and then again as a method of a superclass or another interface? Languages such as Scala and C++ have complex rules for resolving such ambiguities. Fortunately, the rules in Java are much simpler. They are:
<!--[if !supportLists]-->1. <!--[endif]-->Superclasses win. If a superclass provides a concrete method, default methods with the same name and parameter types are simply ignored.
<!--[if !supportLists]-->2. <!--[endif]-->Interfaces clash. If a super interface provides a default method, and another interface supplies a method with the same name and parameter types (default or not), then you must resolve the conflict by overriding that method.
Let's look at the second rule. Consider another interface with a getName
method:
1 2 3 |
|
What happens if you form a class that implements both of them?
1 2 3 |
|
The class inherits two inconsistent getName
methods provided by the Person
and Named
interfaces. Rather than choosing one over the other, the Java compiler reports an error and leaves it up to the programmer to resolve the ambiguity. Simply provide a getName
method in the Student
class. In that method, you can choose one of the two conflicting methods, like this:
1 2 3 4 |
|
Now assume that the Named
interface does not provide a default implementation for getName
:
1 2 3 |
|
Can the Student
class inherit the default method from the Person
interface? This might be reasonable, but the Java designers decided in favor of uniformity. It doesn't matter how two interfaces conflict. If at least one interface provides an implementation, the compiler reports an error, and the programmer must resolve the ambiguity.
If neither interface provides a default for a shared method, then we are in the pre-Java 8 situation and there is no conflict. An implementing class has two choices: implement the method, or leave it unimplemented. In the latter case, the class is itself abstract.
I just discussed name clashes between two interfaces. Now consider a class that extends a superclass and implements an interface, inheriting the same method from both. For example, suppose that Person
is a class and Student
is defined as:
1 |
|
In that case, only the superclass method matters, and any default method from the interface is simply ignored. In our example, Student
inherits the getName
method from Person
, and it doesn't make any difference whether the Named
interface provides a default for getName
or not. This is the "class wins" rule. The "class wins" rule ensures compatibility with Java 7. If you add default methods to an interface, it has no effect on code that worked before there were default methods. But be warned: You can never make a default method that redefines one of the methods in theObject
class. For example, you can't define a default method for toString
or equals
, even though that might be attractive for interfaces such as List
. As a consequence of the "classes win" rule, such a method could never win against Object.toString
or Object.equals
.
Static Methods in Interfaces
As of Java 8, you are allowed to add static methods to interfaces. There was never a technical reason why this should be outlawed: It simply seemed to be against the spirit of interfaces as abstract specifications.
Until now, it has been common to place static methods in companion classes. You find pairs of interfaces and utility classes such as Collection/Collections
or Path/Paths
in the standard library.
Have a look at the Paths
class. It has only a couple of factory methods. You can construct a path from a sequence of strings, such as Paths.get("jdk1.8.0", "jre", "bin")
. In Java 8, you can add this method to the Path
interface:
1 2 3 4 5 6 |
|
Then the Paths
class is no longer necessary.
When you look at the Collections
class, you will find two kinds of methods. A method such as:
1 |
|
would work well as a default method of the List
interface:
1 |
|
You could then simply call list.shuffle()
on any list.
For a factory method, that doesn't work because you don't have an object on which to invoke the method. That is where static interface methods come in. For example,
1 2 |
|
could be a static method of the List
interface. Then you would call List.nCopies(10, "Fred")
instead of Collections.nCopies(10, "Fred")
and it would be clear to the reader that the result is a List
.
It is unlikely that the Java collections library will be refactored in this way, but when you implement your own interfaces, there is no longer a reason to provide a separate companion class for utility methods.
In Java 8, static methods have been added to quite a few interfaces. For example, theComparator
interface has a very useful static comparing method that accepts a "key extraction" function and yields a comparator that compares the extracted keys. To compare Person
objects by name, use Comparator.comparing(Person::name)
.
Conclusion
In this article, I compared strings by length with the lambda expression (first, second) -> Integer.compare(first.length(), second.length())
. But with the static compare
method, we can do much better and simply use Comparator.compare(String::length)
. This is a fitting way of closing this article because it demonstrates the power of working with functions. The compare
method turns a function (the key extractor) into a more complex function (the key-based comparator). Such "higher-order functions" are discussed in more detail in my book, as well as in various online resources for Java 8.