Tech and Media Labs
This site uses cookies to improve the user experience.


Java Lambda Expressions

Jakob Jenkov
Last update: 2018-05-29

Java lambda expressions are new in Java 8. Java lambda expressions are Java's first step into functional programming. A Java lambda expression is thus a function which can be created without belonging to any class. A Java lambda expression can be passed around as if it was an object and executed on demand.

Java lambda expressions are commonly used to implement simple event listeners / callbacks, or in functional programming with the Java Streams API.

If you prefer video, I have a video version of this tutorial in this Java Lambda Expression YouTube Playlist. Here is the first video in this playlist:

Java Lambdas and the Single Method Interface

Functional programming is very often used to implement event listeners. Event listeners in Java are often defined as Java interfaces with a single method. Here is a fictive single method interface example:

public interface StateChangeListener {

    public void onStateChange(State oldState, State newState);

}

This Java interface defines a single method which is called whenever the state changes (in whatever is being observed).

In Java 7 you would have to implement this interface in order to listen for state changes. Imagine you have a class called StateOwner which can register state event listeners. Here is an example:

public class StateOwner {

    public void addStateListener(StateChangeListener listener) { ... }

}

In Java 7 you could add an event listener using an anonymous interface implementation, like this:

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(new StateChangeListener() {

    public void onStateChange(State oldState, State newState) {
        // do something with the old and new state.
    }
});

First a StateOwner instance is created. Then an anonymous implementation of the StateChangeListener interface is added as listener on the StateOwner instance.

In Java 8 you can add an event listener using a Java lambda expression, like this:

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

The lambda expressions is this part:

(oldState, newState) -> System.out.println("State changed")

The lambda expression is matched against the parameter type of the addStateListener() method's parameter. If the lambda expression matches the parameter type (in this case the StateChangeListener interface) , then the lambda expression is turned into a function that implements the same interface as that parameter.

Java lambda expressions can only be used where the type they are matched against is a single method interface. In the example above, a lambda expression is used as parameter where the parameter type was the StateChangeListener interface. This interface only has a single method. Thus, the lambda expression is matched successfully against that interface.

Matching Lambdas to Interfaces

A single method interface is also sometimes referred to as a functional interface. Matching a Java lambda expression against a functional interface is divided into these steps:

  • Does the interface have only one method?
  • Does the parameters of the lambda expression match the parameters of the single method?
  • Does the return type of the lambda expression match the return type of the single method?

If the answer is yes to these three questions, then the given lambda expression is matched successfully against the interface.

Interfaces With Default and Static Methods

From Java 8 a Java interface can contain both default methods and static methods. Both default methods and static methods have an implementation defined directly in the interface declaration. This means, that a Java lambda expression can implement interfaces with more than one method - as long as the interface only has a single unimplemented (AKA abstract) method.

In other words, an interface is still a functional interface even if it contains default and static methods, as long as the interface only contains a single unimplemented (abstract) method. Here is a video version of this little section:

The following interface can be implemented with a lambda expression:

import java.io.IOException;
import java.io.OutputStream;

public interface MyInterface {

    void printIt(String text);

    default public void printUtf8To(String text, OutputStream outputStream){
        try {
            outputStream.write(text.getBytes("UTF-8"));
        } catch (IOException e) {
            throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);
        }
    }

    static void printItToSystemOut(String text){
        System.out.println(text);
    }
}

Even though this interface contains 3 methods it can be implemented by a lambda expression, because only one of the methods is unimplemented. Here is how the implementation looks:

MyInterface myInterface = (String text) -> {
    System.out.print(text);
};

Lambda Expressions vs. Anonymous Interface Implementations

Even though lambda expressions are close to anonymous interface implementations, there are a few differences that are worth noting.

The major difference is, that an anonymous interface implementation can have state (member variables) whereas a lambda expression cannot. Look at this interface:

public interface MyEventConsumer {

    public void consume(Object event);

}

This interface can be implemented using an anonymous interface implementation, like this:

MyEventConsumer consumer = new MyEventConsumer() {
    public void consume(Object event){
        System.out.println(event.toString() + " consumed");
    }
};

This anonymous MyEventConsumer implementation can have its own internal state. Look at this redesign:

MyEventConsumer myEventConsumer = new MyEventConsumer() {
    private int eventCount = 0;
    public void consume(Object event) {
        System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");
    }
};

Notice how the anonymous MyEventConsumer implementation now has a field named eventCount.

A lambda expression cannot have such fields. A lambda expression is thus said to be stateless.

Lambda Type Inference

Before Java 8 you would have to specify what interface to implement, when making anonymous interface implementations. Here is the anonymous interface implementation example from the beginning of this text:

stateOwner.addStateListener(new StateChangeListener() {

    public void onStateChange(State oldState, State newState) {
        // do something with the old and new state.
    }
});

With lambda expressions the type can often be inferred from the surrounding code. For instance, the interface type of the parameter can be inferred from the method declaration of the addStateListener() method (the single method on the StateChangeListener interface). This is called type inference. The compiler infers the type of a parameter by looking elsewhere for the type - in this case the method definition. Here is the example from the beginning of this text, showing that the StateChangeListener interface is not mentioned in the lambda expression:

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

In the lambda expression the parameter types can often be inferred too. In the example above, the compiler can infer their type from the onStateChange() method declaration. Thus, the type of the parameters oldState and newState are inferred from the method declaration of the onStateChange() method.

Lambda Parameters

Since Java lambda expressions are effectively just methods, lambda expressions can take parameters just like methods. The (oldState, newState) part of the lambda expression shown earlier specifies the parameters the lambda expression takes. These parameters have to match the parameters of the method on the single method interface. In this case, these parameters have to match the parameters of the onStateChange() method of the StateChangeListener interface:

public void onStateChange(State oldState, State newState);

As a minimum the number of parameters in the lambda expression and the method must match.

Second, if you have specified any parameter types in the lambda expression, these types must match too. I haven't shown you how to put types on lambda expression parameters yet (it is shown later in this text), but in many cases you don't need them.

Zero Parameters

If the method you are matching your lambda expression against takes no parameters, then you can write your lambda expression like this:

() -> System.out.println("Zero parameter lambda");

Notice how the parentheses have no content in between. That is to signal that the lambda takes no parameters.

One Parameter

If the method you are matching your Java lambda expression against takes one parameter, you can write the lambda expression like this:

(param) -> System.out.println("One parameter: " + param);

Notice the parameter is listed inside the parentheses.

When a lambda expression takes a single parameter, you can also omit the parentheses, like this:

 param -> System.out.println("One parameter: " + param);

Multiple Parameters

If the method you match your Java lambda expression against takes multiple parameters, the parameters need to be listed inside parentheses. Here is how that looks in Java code:

(p1, p2) -> System.out.println("Multiple parameters: " + p1 + ", " + p2);

Only when the method takes a single parameter can the parentheses be omitted.

Parameter Types

Specifying parameter types for a lambda expression may sometimes be necessary if the compiler cannot infer the parameter types from the functional interface method the lambda is matching. Don't worry, the compiler will tell you when that is the case. Here is a Java lambda parameter type example:

(Car car) -> System.out.println("The car is: " + car.getName());

As you can see, the type (Car) of the car parameter is written in front of the parameter name itself, just like you would when declaring a parameter in a method elsewhere, or when making an anonymous implementation of an interface.

Lambda Function Body

The body of a lambda expression, and thus the body of the function / method it represents, is specified to the right of the -> in the lambda declaration: Here is an example:

 (oldState, newState) -> System.out.println("State changed")

If your lambda expression needs to consist of multiple lines, you can enclose the lambda function body inside the { } bracket which Java also requires when declaring methods elsewhere. Here is an example:

 (oldState, newState) -> {
    System.out.println("Old state: " + oldState);
    System.out.println("New state: " + newState);
  }

Returning a Value From a Lambda Expression

You can return values from Java lambda expressions, just like you can from a method. You just add a return statement to the lambda function body, like this:

 (param) -> {
    System.out.println("param: " + param);
    return "return value";
  }

In case all your lambda expression is doing is to calculate a return value and return it, you can specify the return value in a shorter way. Instead of this:

 (a1, a2) -> { return a1 > a2; }

You can write:

 (a1, a2) -> a1 > a2;

The compiler then figures out that the expression a1 > a2 is the return value of the lambda expression (hence the name lambda expressions - as expressions return a value of some kind).

Lambdas as Objects

A Java lambda expression is essentially an object. You can assign a lambda expression to a variable and pass it around, like you do with any other object. Here is an example:

public interface MyComparator {

    public boolean compare(int a1, int a2);

}
MyComparator myComparator = (a1, a2) -> return a1 > a2;

boolean result = myComparator.compare(2, 5);

The first code block shows the interface which the lambda expression implements. The second code block shows the definition of the lambda expression, how the lambda expression is assigned to variable, and finally how the lambda expression is invoked by invoking the interface method it implements.

Variable Capture

A Java lambda expression is capable of accessing variables declared outside the lambda function body under certain circumstances. I have a video version of this section here:

Java lambdas can capture the following types of variables:

  • Local variables
  • Instance variables
  • Static variables

Each of these variable captures will described in the following sections.

Local Variable Capture

A Java lambda can capture the value of a local variable declared outside the lambda body. To illustrate that, first look at this single method interface:

public interface MyFactory {
    public String create(char[] chars);
}

Now, look this lambda expression that implements the MyFactory interface:

MyFactory myFactory = (chars) -> {
    return new String(chars);
};

Right now this lambda expression is only referencing the parameter value passed to it (chars). But we can change that. Here is an updated version that references a String variable declared outside the lambda function body:

String myString = "Test";

MyFactory myFactory = (chars) -> {
    return myString + ":" + new String(chars);
};

As you can see, the lambda body now references the local variable myString which is declared outside the lambda body. This is possible if, and only if, the variable being references is "effectively final", meaning it does not change its value after being assigned. If the myString variable had its value changed later, the compiler would complain about the reference to it from inside the lambda body.

Instance Variable Capture

A lambda expression can also capture an instance variable in the object that creates the lambda. Here is an example that shows that:

public class EventConsumerImpl {

    private String name = "MyConsumer";

    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(this.name);
        });
    }
}

Notice the reference to this.name inside the lambda body. This captures the name instance variable of the enclosing EventConsumerImpl object. It is even possible to change the value of the instance variable after its capture - and the value will be reflected inside the lambda.

The semantics of this is actually one of the areas where Java lambdas differ from anonymous implementations of interfaces. An anonymous interface implementation can have its own instance variables which are referenced via the this reference. However, an lambda cannot have its own instance variables, so this always points to the enclosing object.

Note: The above design of an event consumer is not particularly elegant. I just made it like that to be able to illustrate instance variable capture.

Static Variable Capture

A Java lambda expression can also capture static variables. This is not surprising, as static variables are reachable from everywhere in a Java application, provided the static variable is accessible (packaged scoped or public).

Here is an example class that creates a lambda which references a static variable from inside the lambda body:

public class EventConsumerImpl {
    private static String someStaticVar = "Some text";

    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(someStaticVar);
        });
    }
}

The value of a static variable is also allowed to change after the lambda has captured it.

Again, the above class design is a bit nonsensical. Don't think too much about that. The class primarily serves to show you that a lambda can access static variables.

Method References as Lambdas

In the case where all your lambda expression does is to call another method with the parameters passed to the lambda, the Java lambda implementation provides a shorter way to express the method call. First, here is an example single function interface:

public interface MyPrinter{
    public void print(String s);
}

And here is an example of creating a Java lambda instance implementing the MyPrinter interface:

MyPrinter myPrinter = (s) -> { System.out.println(s); };

Because the lambda body only consists of a single statement, we can actually omit the enclosing { } brackets. Also, since there is only one parameter for the lambda method, we can omit the enclosing ( ) brackets around the parameter. Here is how the resulting lambda declaration looks:

MyPrinter myPrinter = s -> System.out.println(s);

Since all the lambda body does is forward the string parameter to the System.out.println() method, we can replace the above lambda declaration with a method reference. Here is how a lambda method reference looks:

MyPrinter myPrinter = System.out::println;

Notice the double colons :: . These signal to the Java compiler that this is a method reference. The method referenced is what comes after the double colons. Whatever class or object that owns the referenced method comes before the double colons.

You can reference the following types of methods:

  • Static method
  • Instance method on parameter objects
  • Instance method
  • Constructor

Each of these types of method references are covered in the following sections.

Static Method References

The easiest methods to reference are static methods. Here is first an example of a single function interface:

public interface Finder {
    public int find(String s1, String s2);
}

And here is a static method that we want to create a method reference to:

public class MyClass{
    public static int doFind(String s1, String s2){
        return s1.lastIndexOf(s2);
    }
}

And finally here is a Java lambda expression referencing the static method:

Finder finder = MyClass::doFind;

Since the parameters of the Finder.find() and MyClass.doFind() methods match, it is possible to create a lambda expression that implements Finder.find() and references the MyClass.doFind() method.

Parameter Method Reference

You can also reference a method of one of the parameters to the lambda. Imagine a single function interface that looks like this:

public interface Finder {
    public int find(String s1, String s2);
}

The interface is intended to represent a component able to search s1 for occurrences of s2. Here is an example of a Java lambda expression that calls String.indexOf() to search:

Finder finder = String::indexOf;

This is equivalent of this lambda definition:

Finder finder = (s1, s2) -> s1.indexOf(s2);

Notice how the shortcut version references a single method. The Java compiler will attempt to match the referenced method against the first parameter type, using the second parameter type as parameter to the referenced method.

Instance Method References

Third, it is also possible to reference an instance method from a lambda definition. First, let us look at a single method interface definition:

public interface Deserializer {
    public int deserialize(String v1);
}

This interface represents a component that is capable of "deserializing" a String into an int.

Now look at this StringConverter class:

public class StringConverter {
    public int convertToInt(String v1){
        return Integer.valueOf(v1);
    }
}

The convertToInt() method has the same signature as the deserialize() method of the Deserializer deserialize() method. Because of that, we can create an instance of StringConverter and reference its convertToInt() method from a Java lambda expression, like this:

StringConverter stringConverter = new StringConverter();

Deserializer des = stringConverter::convertToInt;

The lambda expression created by the second of the two lines references the convertToInt method of the StringConverter instance created on the first line.

Constructor References

Finally it is possible to reference a constructor of a class. You do that by writing the class name followed by ::new, like this:

MyClass::new

Too see how to use a constructor as a lambda expression, look at this interface definition:

public interface Factory {
    public String create(char[] val);
}

The create() method of this interface matches the signature of one of the constructors in the String class. Therefore this constructor can be used as a lambda. Here is an example of how that looks:

Factory factory = String::new;

This is equivalent to this Java lambda expression:

Factory factory = chars -> new String(chars);

Jakob Jenkov




Copyright  Jenkov Aps
Close TOC