Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Lambda Expressions in Java 8

4.70/5 (12 votes)
2 Jun 2014CPOL22 min read 34.9K  
The article describes using the new feature of Java 8 - lambda expressions.

Introduction

In new Java 8 we at last have lambda expressions. Maybe it is the most important feature in the latest version, it enables faster, clearer coding and opens the door to functional programming. Here's how it works.

Java is an object-oriented programming language that was created in the 90s, when object-oriented paradigm was the software development mainstream. Long before there were functional programming languages such as Lisp, but their benefits were not much appreciated outside academic circles. Recently, functional programming has become more important because it is well suited for concurrent and event-driven programming. That doesn't mean that object orientation is sidelined or bad now. Instead, the best strategy is mixing these two paradigms. This can be useful even if you are not interested in parallel programming. For example, collection libraries can have powerful APIs if the language supports a convenient syntax for functional expressions.

The major improvement in Java 8 is the addition of support for functional programming to its object-oriented basis. In this article, I show the basic syntax and examine how to use it in several different contexts.

Why do we need lambdas?

A lambda expression is a code block that you can pass around so you can execute it later, only once or multiple times. Before diving into the syntax, let's remember where you used similar code structure in the development process before.

Let’s consider sample of sorting using a custom comparator. If you want to sort strings by length instead of the dictionary order (by default), you should pass a Comparator object to the method sort:

Java
class LengthStringComparator implements Comparator<String> {
    public int compare(String firstStr, String secondStr) {
        return Integer.compare(firstStr.length(), secondStr.length());
    }
} 
Arrays.sort(strings, new LengthStringComparator ()); 

The sort method calls the compare method, rearranging the items if they are not ordered, until the array is sorted. You provide the sort method a code snippet for comparing elements, and it integrates with the rest of the sorting logic, which you need not 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. It is a static method, added to Java 7. You shouldn't compute the difference x - y to compare x and y because it can overflow for large operands of opposite sign.

If you want to perform some action in another thread, you put these steps in a method run of a Runnable, as follows:

Java
class MyRunner implements Runnable {
    public void run() {
        for (int i = 0; i < 1000; i++)
            doWork();
    } 
    ... 
} 

Then, when you want to execute this code, you instantiate a MyRunner object. You can then put the instance to a thread pool, or simply start a new thread:

Java
MyRunner r = new MyRunner();
new Thread(r).start(); 

The key point is that the method run contains code that you want to execute in a separate thread.

Another example for deferred execution is a button callback. You create a class that implements the listener interface and place the action callback into its method, create a class instance, and register the instance with the button. It is so common case that many programmers often use the "anonymous instance of anonymous class" syntax:

Java
button.setOnAction(new EventHandler<ActionEvent>() {
    public void handle(ActionEvent event) {
        System.out.println("The button has been clicked!");
    } 
}); 

You should notice that this code is executed whenever the button is clicked.

In my samples I use JavaFX, because Java 8 positions JavaFX as the successor to the Swing GUI toolkit. The details don't matter. Every user interface toolkit, be it Swing, JavaFX, has the same idiom - you give a button some piece of code that you want to run when the button is clicked.

In all three code samples, you see the similar idea. You pass a code block to someone — a thread pool, a sort method, or a button. And this code is called at some later time.

Earlier passing someone a block of code was not so easy in Java. Java is an object-oriented language, so you had to create a special class with a method that contains the desired code and then instantiate a class object.

In other languages, such as C#, you can work with blocks of code directly. Language designers have opposed adding this feature for a many years. First of all, a great strength of Java is its simplicity and consistency. A language can become a messy pile of garbage if it includes all the options, which give a little shorter code. However, in those other languages, it isn't just easier to spawn a thread or to bind a button-click handler. As a result a lot of libraries are simpler, more consistent, and more powerful. In Java, one could have written similar APIs in an old style, but it would be not so convenient for use.

For the last few years, the question was not whether to add elements of functional programming to Java or not, but how to do it. It took some time to experiment before it became clear that these features fit for Java. In the next section, you will see how to use blocks of code in Java.

The Syntax of Lambdas

Let’s get back again to the sorting sample. We provide the code that determines which string is shorter. We compute

Java
Integer.compare(firstStr.length(), secondStr.length())

What are the objects firstStr and secondStr? They are both strings! Java is a strongly typed language, so we must specify the argument types as well:

Java
(String firstStr, String secondStr)
    -> Integer.compare(firstStr.length(),secondStr.length()) 

You have just written your first lambda expression! This expression specifies a block of code and the variables that must be passed to the code.

And a bit of history… Why such a name? A long time ago, when about any computers yet no one thought, mathematician 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 symbol lambda (λ) to mark parameters.

Why he used exactly that letter? Has he really used up all the letters of the alphabet? Church borrowed the idea of ​​using this letter from a famous work in mathematics Principia Mathematica, where the free variables were marked by the ˆ accent, and it inspired Church to use an uppercase lambda (Λ) for parameters of functions. However, in the end, he decided to use the lowercase version. Ever since, an expression with parameter variables has been called a "lambda expression."

There are several slightly different forms of Java lambdas. Let’s consider them more closely. You have just seen one of them: parameters, the -> arrow, and an expression. If the code contains a computation that doesn't fit in a single expression, write it exactly like you would have written a method: put the code into {} and add explicit return statements. For example,

Java
(String firstStr, String secondStr) -> {
    if (firstStr.length() < secondStr.length()) return -1;
    else if (firstStr.length() > secondStr.length()) return 1;
    else return 0;
} 

If there are no arguments in lambda, you still should place empty parentheses, just like a parameterless method:

Java
() -> { for (int i = 0; i < 1000; i++) doSomething(); }

If the lambda’s argument types can be inferred, you can omit them. For example,

Java
Comparator<String> comp
= (firstStr, secondStr) // Same as (String firstStr, String secondStr)
-> Integer.compare(firstStr.length(),secondStr.length());

At this point, the compiler can figure out that firstStr and secondStr are strings because we assign the lambda to a string comparator. (We will look closer at this code later.)

If a method has only one parameter, which type can be deduced by the compiler, you can even omit the parentheses:

Java
EventHandler<ActionEvent> listener = event ->
System.out.println("The button has been clicked!");
// Instead of (event) -> or (ActionEvent event) -> 

Besides, you can place the final modifier and annotations to lambda arguments the way you do it for method arguments:

Java
(final String var) -> ...
(@NonNull String var) -> ...

You never need to specify the result type of a lambda expression. The compiler always deduces it from the context. For example, you can use the lambda

Java
(String firstStr, String secondStr) -> Integer.compare(firstStr.length(), secondStr.length()) 

where int is expected as a result type .

Note that in lambda you can’t return a value not in all the branches. For example, (int x) -> { if (x <= 1) return -1; } is invalid.

Functional Interfaces

As we have just discussed, Java has a lot of existing interfaces that encapsulate blocks of code, such as Runnable or Comparator. Lambdas suit for these purposes.

In Java there are so-called functional interfaces – it is an object that implements interface with a single abstract method. You can provide a lambda expression whenever an object of a functional interface is expected.

You may ask why a functional interface must have a single abstract method. As we know all methods in an interface are abstract, aren’t they? Actually, it was always 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. The Comparator API may be as an example.) More importantly, as you will see below, we can declare non-abstract methods in interfaces in Java 8.

Let’s consider the example with Arrays.sort method. Here we can see the substitution of functional interface with lambda. We simply pass a lambda to the method as a second parameter that requires a Comparator object, an interface with a single method.

Java
Arrays.sort(strs,
(firstStr, secondStr) -> Integer.compare(firstStr.length(), secondStr.length()));

In fact the Arrays.sort method receives an object of some class implementing Comparator<String>. When the compare method is called it forces the lambda expression body to be executed. The structure of these objects and classes completely depends on implementation. It can use not only the traditional inner classes. Maybe it is better to represent lambda as a function, not as an object and find out that we can pass it to a functional interface.

This conversion to interfaces is why lambda expressions are so exciting. The syntax is short and simple. Here is another example:

Java
button.setOnAction(event ->
System.out.println("The button has been clicked!")); 

Do you agree that it's very easy to read?

In fact, the only thing that you can do with a lambda expression in Java is such a conversion. In other programming languages, such as C#, you can declare lambda types such as (String, String) -> int, declare variables of those types, and use the variables to save function expressions. In Java, don’t even try to assign a lambda expression to a variable of type Object because Object is not a functional interface. Java designers decided to limit operations with lambdas instead of adding special types to the language.

There are several generic functional interfaces in the package java.util.function in Java API. One of them, BiFunction<T, U, R>, represents a function with parameter types T and U and return type R. You can assign your string comparison lambda to such a variable:

Java
BiFunction<String, String, Integer> compareFunc
= (firstStr, secondStr) -> Integer.compare(firstStr.length(), secondStr.length());

However, it does not suit for our purpose. There is no Arrays.sort method that requires a BiFunction. It may be curious for experienced functional language developer. However it is common in Java. Interfaces like Comparator are intended for specific cases, not just a method with some parameters and return type. It’s a Java 8 style.

You can see these interfaces from java.util.function in different Java 8 APIs. In Java 8 any functional interface can be marked with the @FunctionalInterface. This annotation is optional, but it is a good style for two reasons. First, it forces the compiler to check that the annotated entity is an interface with a single abstract method. The second advantage is the javadoc page includes a statement that your interface is a functional interface. Any interface with only one abstract method is a functional interface by definition. However the use of this keyword is making greater clarity.

By the way, keep in mind that checked exceptions can appear while a conversion of a lambda to a functional interface. If the body of a lambda expression can throw a checked exception, you should declare this exception in the abstract method of the target interface. For example, the following code will cause an error:

Java
Runnable sleepingRunner = () -> { System.out.println("…"); Thread.sleep(1000); };
// Error: Thread.sleep can throw a checkedInterruptedException

This statement is incorrect, because the run method cannot throw any exception. There are two ways to fix the problem. One way is to catch the exception in the lambda body. The second one is assigning this lambda to an interface with single abstract method that can throw the exception. For example, the method call of the interface Callable can generate any exception. Therefore, if you add return null at the end of lambda body, you can assign your lambda to a Callable<Void> instance.

Method References

Sometimes you already have somewhere a method that suits for your needs and you'd like to pass it to some other call. For example, imagine you wish to simply print the event object whenever a button is clicked. Of course, you could write something like this

Java
button.setOnAction(event -> System.out.println(event));

It would be more convenient for you just to pass the println method to the method setOnAction. The following example shows it:

Java
button.setOnAction(System.out::println); 

System.out::println is a method reference that is analogous to the lambda expression. We can replace a lambda with a method reference here.

Imagine you want to sort strings ignoring a letter case. You can write a code as follows:

Java
Arrays.sort(strs, String::compareToIgnoreCase)

The operator :: separates the method name from the name of an object or class. There are three main cases:

  • instance method of an object;
  • static method of a class;
  • instance method of a class;

In the first two cases, the method reference is equivalent to a lambda expression with method parameters. As you can see above, 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).

As you know, there can be multiple overloaded methods with the same name. In such cases the compiler will try to find from the context which one to choose. For instance, there are two versions of the Math.max method, one for ints and one for double values. Which one to call depends on the method signature of the functional interface to which Math::max is converted. Method references do not live by themselves alone. Similar to lambdas behind the scenes they are always turned into instances of functional interfaces.

You may ask if you can capture the parameter this in a method reference. Yes, you can. For example, this::equals is equivalent to x -> this.equals(x). It also possible to use super. When we use super::instanceMethod this becomes the target and the base class version of the given method is called. Here is a pretty naive example:

Java
class Speaker {
    public void speak() {
       System.out.println("Hello, world!");
    } 
} 
class ConcurrentSpeaker extends Speaker {
    public void speak() {
        Thread t = new Thread(super::speak);
        t.start();
    } 
} 

When the thread starts, run method is invoked, and super::speak is executed, calling the speak method of its base class. 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 the same as method references, except that the method name is new. For example, Button::new is a constructor reference for class Button. Which constructor will be invoked depends on the context. Imagine you want to turn a list of strings to an array of buttons. In this case you should call the constructor on each string. It may be something like this:

Java
List<String> strs = ...;
Stream<Button> stream = strs.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());

For more information about the stream, map, and collect methods you can view the documentation. In this case, the most important is that the map method calls the Button(String) constructor for each list element. There are multiple Button constructors, but the compiler selects the one with a String argument because it is obvious from the context that the constructor with a string should be called.

There is also a possibility to create constructor references for array types. As an example, a constructor reference for int array is int[]::new. It takes one parameter: the length of the array. It is equivalent to the lambda expression x -> new int[x].

Constructor reference for array is useful for going beyond limitations of Java. It is impossible to create an array of a generic type T. The expression new T[n] is incorrect since it will be replaced with new Object[n]. It is a problem for library authors. Imagine, we want to have an array of buttons. There is a method toArray in class Stream that returns an array of Object:

Java
Object[] buttons = stream.toArray();

But that is not what you wanted. The user does not want Objects, only buttons. The library solves this problem using constructor references. You should pass Button[]::new to the method toArray:

Java
Button[] buttons = stream.toArray(Button[]::new); 

The toArray method calls this constructor to get an array of the needed type, and then it returns this array after filling it.

Variable Scope

Often, you would like to access variables from an enclosing scope in a lambda expression. Consider this code:

Java
public static void repeatText(String text, int count) {
    Runnable r = () -> {
        for (int i = 0; i < count; i++) { 
            System.out.println(text);
            Thread.yield();
        } 
    }; 
    new Thread(r).start();
} 

Consider a call:

Java
repeatText("Hi!", 2000); // Prints Hi 2000 times in a separate thread 

Note that the variables count and text are not defined in the lambda expression; these are parameters of the enclosing method.

If you look at this code closely, you will realize that there is some kind of magic behind the scenes. The repeatText method may return before the code of the lambda expression run and by that moment the parameter variables are gone, but they are still available to the lambda. What is the secret?

To find out what is going on, we need to improve our understanding of a lambda expression. A lambda expression consists of three components:

  1. A code block
  2. Arguments
  3. The free variables that are not parameters and not defined inside the lambda

There are two free variables in our case, text and count. The data structure representing the lambda must store their values, "Hi!" and 2000. They say that these values are captured by the lambda expression. (How it is done depends on the implementation. For example, implementation can turn a lambda expression into an object with one method and copy the values of the free variables into instance variables of the object.)

There is a special term “closure”; it is a block of code together with the values of the free variables. Lambdas represent closures in Java with a convenient syntax. By the way, inner classes always have been closures.

So, a lambda expression can capture the value of a variable in the enclosing scope, but there are some restrictions for that. You cannot change the value of the captured variable. The following code is incorrect:

Java
public static void repeatText(String text, int count) {
    Runnable r = () -> {
        while (count > 0) {
            count--; // Error: Can't modify captured variable
            System.out.println(text);
            Thread.yield();
        }
    };
    new Thread(r).start();
} 

This limitation is justified because mutating variables in a lambda expression is not thread-safe. Imagine that we have a sequence of concurrent tasks, each updating a shared counter.

Java
int matchCount = 0;
for (Path p : files)
    new Thread(() -> { if (p has some property) matchCount++; }).start();
    // Illegal to change matchCount 

It would be very, very bad, if this code was correct. The increment operator ++ is not atomic, and there is no way to control the result if multiple threads execute this code concurrently.

Inner classes can also capture values from an outer class. Before Java 8, inner classes could access only final local variables. This rule was extended to match that for lambda expressions. Now an inner class can work with any variable whose value does not change (effectively final variable).

Don't count on the compiler to catch all concurrent access errors. You should be aware, that this rule against modification holds only for local variables. If we work with an instance or static variable of an outer class, then no error will appear, and the result is undefined.

You can also modify a shared object, even though it is unsound. For example,

Java
List<Path> matchedObjs = new ArrayList<>();
for (Path p : files)
    new Thread(() -> { if (p has some property) matchedObjs.add(p); }).start();
    // Legal to change matchedObjs, but unsafe 

If you look closely you will see that the variable matchedObjs is effectively final. (An effectively final variable is a variable that is never assigned a new value after its initialization.) In this code, matchedObjs always refers to the same object. However, the variable matchedObjs is modified, and it is not thread-safe. The result of running this code in multithreaded environment is unpredictable.

There are safe mechanisms for such multithreaded tasks. For example, you may use thread-safe counters and collections, streams to collect values.

In case of an inner class, there is a workaround that lets a lambda expression update a counter in an enclosing local scope. Imagine you use an array of length 1, like this:

Java
int[] counts = new int[1];
button.setOnAction(event -> counts[0]++); 

It is clear that this code is not thread-safe. For a button callback, that doesn't matter, but in general, you should think twice before using this trick.

The lambda’s body has the same scope as a nested block. The rules for name conflicts and shadowing are the same hear. You cannot declare an argument or a local variable in the lambda that has the same name as a variable in an enclosing scope.

Java
Path first = Paths.get("/usr/local");
Comparator<String> comp =
    (first, second) -> Integer.compare(first.length(), second.length());
    // Error: Variable first already defined  

You can't have two local variables with the same name inside a method. Therefore, you can't introduce such variables in a lambda expression either. The same rule applies for lambda. When you use the this keyword inside a lambda, you refer to the this of the method that creates the lambda. Let’s consider this code

Java
public class Application() { 
    public void doSomething() { 
        Runnable r = () -> { ...; System.out.println(this.toString()); ... }; 
        ... 
    } 
} 

In this example <code>this.toString() calls the toString method of the Application object, not the Runnable instance. There is nothing special about the use of this in a lambda expression. The scope of the lambda expression is nested inside the doSomething method, and this has the same meaning anywhere in that method.

Default Methods

And now, finally, let's talk about the new feature that is not directly related to lambdas, but it is also very interesting – default methods.

In many programming languages function expressions are integrated with their collections library. This often leads to code that is shorter and easier to understand than its equivalent using loops. Let’s consider the code:

Java
for (int i = 0; i < strList.size(); i++)
    System.out.println(strList.get(i));

We can improve this code. The library can supply a convenient method forEach that applies a function to each element. So you can simplify the code

Java
strList.forEach(System.out::println);

Everything is OK if the library has been designed from scratch, but what if it was created long time ago like in Java? If the Collection interface adds a new method, such as forEach, then every application that defines its own class implementing this interface will not work until it, too, implements that method. It is not good in Java.

This problem in Java was solved by allowing interface methods with concrete implementations (named default methods). You can add such methods safely to existing interfaces. Now we'll look at default methods more closely. In Java 8, the forEach method has been added to the Iterable, a base interface of Collection, using the tricks that you will see below.

Consider this interface:

Java
interface Person { 
    long getId(); 
    default String getFirstName() { return "Jack"; } 
} 

There are two methods in the interface: getId, which is an abstract method, and the getFirstName, which is the default one. A concrete class that implements this interface must, of course, give an implementation of getId, but it can choose to use the default implementation of getFirstName or to override it.

This technique puts stops using the classic pattern of providing an interface and an abstract class that implements most or all of its methods, such as Collection/AbstractCollection or WindowListener/WindowAdapter. Now, you can implement the needed methods in the interface.

What will happen if a method with the identical signature is defined as a default method in one interface and then again in a base class or another interface? Other languages such as C++ have complex rules for resolving such ambiguities. Fortunately, the rules in Java are much simpler. Let’s look at them:

  1. Base classes win. If a base class contains a concrete method, default methods with the same signature are just ignored.
  2. Interfaces clash. If a base interface has a default method, and another interface contains a method with identical signature (default or not), then you must resolve the conflict manually by overriding the method.

Let's look closely at the second rule. Imagine another interface with a getFirstName method:

Java
interface Naming {
    default String getFirstName() { return getClass().getName() + "_" + hashCode(); } 
} 

What happens if you try to create a class that implements both of them?

Java
class Student implements Person, Naming { 
    ... 
} 

This class inherits two different methods getFirstName supplied by the interfaces Person and Naming. 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 implement a getFirstName method in the class Student. And then in that method, you can choose one of the two conflicting methods, like this:

Java
class Student implements Person, Naming { 
    public String getFirstName() { returnPerson.super.getFirstName (); } 
    ... 
} 

Now let’s consider the case that the Naming interface does not supply a default implementation for getFirstName:

Java
interface Naming { 
    String getFirstName(); 
} 

Will the Student class inherit the default method from the Person interface? The Java designers decided in favor of uniformity, although this way might be reasonable. No matter exactly how interfaces clash. If at least one interface contains an implementation, the compiler reports a conflict, and the programmer must resolve the problem manually.

If none of the interfaces provide a default implementation of the method, then there is no problem. A final class may either implement the method, or leave it unimplemented. In the second case, the class itself remains abstract.

I just discribed name clashes between two interfaces. Now let’s consider a class that inherits another class and implements an interface, inheriting the same method from both. For example, suppose that Person is a class, Naming is an interface and class Student is defined as:

Java
class Student extends Person implements Naming { ... }  

In such cases, any default method from the interface is simply ignored. In our sample, Student inherits the getFirstName method from Person, and it doesn't matter whether the Naming interface provides a default implementation for getFirstName or not. This is the "class wins" rule in action. This rule ensures compatibility with Java 7. If you add default methods to an interface, it has no effect on an old code. But please keep in mind that you can never create the default method that overrides one of the methods in the class Object. For instance, you cannot redefine a default method for toString or equals, although it might be useful for interfaces such as List. As a result of the rule, such a method could never win against Object.toString or Object.equals.

Default methods allow you to add new functionality to existing interfaces and ensure binary compatibility with code written for older versions of those interfaces. In particular, using default methods you can add methods that accept lambda expressions as parameters to existing interfaces.

Conclusion

I have described lambda expressions as the largest upgrade to the programming model ever — maybe larger even than generics. And why not? It gives Java programmer the edge that it lacked compared to other functional programming languages. Along with other features like default methods, lambdas can be used to write some really good code.

I hope this article gave a glimpse of what Java 8 can offer to us.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)