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,
(String first, String second) -> { if (first.length() < second.length()) return -1; else if (first.length() > second.length()) return 1; else return 0; }
If a lambda expression has no parameters, you still supply empty parentheses, just as with a parameterless method:
() -> { for (int i = 0; i < 1000; i++) doWork(); }
If the parameter types of a lambda expression can be inferred, you can omit them. For example,
Comparator<String> comp = (first, second) // Same as (String first, String second) -> Integer.compare(first.length(), second.length());
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:
EventHandler<ActionEvent> listener = event -> System.out.println("Thanks for clicking!"); // Instead of (event) -> or (ActionEvent event) ->
You can add annotations or the final
modifier to lambda parameters in the same way as for method parameters:
(final String name) -> ... (@NonNull String name) -> ...
You never specify the result
type of a lambda expression. It is always inferred from context. For example, the expression
(String first, String second) -> Integer.compare(first.length(), second.length())
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:
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length()));
Behind the scenes, the Arrays.sort
method receives an object of some class that implements Comparator<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:
button.setOnAction(event -> System.out.println("Thanks for clicking!"));
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:
BiFunction<String, String, Integer> comp = (first, second) -> Integer.compare(first.length(), second.length());
However, that does not help you with sorting. There is no Arrays.sort
method that wants a BiFunction
. 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:
Runnable sleeper = () -> { System.out.println("Zzz"); Thread.sleep(1000); }; // Error: Thread.sleep can throw a checkedInterruptedException
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
button.setOnAction(event -> System.out.println(event));
It would be nicer if you could just pass the println
method to the setOnAction
method. Here is how you do that:
button.setOnAction(System.out::println);
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:
Arrays.sort(strings, String::compareToIgnoreCase)
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:
object::instanceMethod
Class::staticMethod
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.max
method, 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 expression super::instanceMethod
uses this as the target and invokes the superclass version of the given method. Here is an artificial example that shows the mechanics:
class Greeter { public void greet() { System.out.println("Hello, world!"); } } class ConcurrentGreeter extends Greeter { public void greet() { Thread t = new Thread(super::greet); t.start(); } }
When the thread starts, its Runnable
is invoked, and super::greet
is executed, calling the greet
method 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 is new
. 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:
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 list
element. There are multiple Button
constructors, but the compiler picks the one with a String
parameter 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 expression x -> 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 Object
array:
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:
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.