“This is the fifth day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

Java lambda expressions are new in Java 8. Java lambda expressions are Java’s first step into functional programming. Thus, a Java lambda expression is a function that can be created without belonging to any class. Java lambda expressions can be passed like objects and executed as needed.

Java lambda expressions are often used to implement simple event listeners/callbacks, or for functional programming using the [Java Streams API]. Java Lambda expressions are also often used for [functional programming in Java]

Java Lambdas and the single-method interface

Functional programming is often used to implement event listeners. Event listeners in Java are typically defined as Java interfaces that use a single method. Here is an example of a fictitious single-method interface:

public interface StateChangeListener {

    public void onStateChange(State oldState, State newState);

}
Copy the code

This Java interface defines a method that is invoked whenever there is a state change (no matter what is observed).

In Java 7, you must implement this interface to listen for state changes. Imagine that you have a class called StateOwner that registers State Event listeners. Here’s an example:

public class StateOwner { public void addStateListener(StateChangeListener listener) { ... }}Copy the code

In Java 7, you can add event listeners using an anonymous interface implementation, as follows:

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(new StateChangeListener() {

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

Start by creating a StateOwner instance. The anonymous implementation of the StateChangeListener interface is then added as a listener on the StateOwner instance.

In Java 8, you can add event listeners using Java lambda expressions, as follows:

StateOwner stateOwner = new StateOwner();

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

Lambda expressions are this part:

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

The lambda expression matches the parameter type of the addStateListener() method parameter. If the lambda expression matches the type of the argument (in this case, the StateChangeListener interface), the lambda expression is converted to a function that implements the same interface as the argument.

Java lambda expressions can only be used if the matching type is a single method interface. In the above example, a lambda expression is used as a parameter, where the parameter type is the StateChangeListener interface. This interface has only one method. Therefore, the lambda expression successfully matches the interface.

Match Lambdas to interfaces

Individual method interfaces are sometimes called functional interfaces. Matching Java lambda expressions with function interfaces is divided into the following steps:

  • Does the interface have only one abstract (unimplemented) method?
  • Do lambda expression arguments match those of a single method?
  • Does the return type of a lambda expression match the return type of a single method?

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

Interfaces with default and static methods

From Java 8, the [Java interface] can contain both default and static methods. Both the default method and the static method have an implementation defined directly in the interface declaration. This means that Java lambda expressions can implement interfaces using multiple methods — as long as the interface has only one unimplemented (AKA abstract) method.

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

The following interfaces can be implemented using lambda expressions:

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); }}Copy the code

Even if this interface contains three methods, it can be implemented through lambda expressions because only one method is not implemented. Here’s what the implementation looks like:

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

Lambda expressions with anonymous interface implementations

Although lambda expressions are close to anonymous interface implementations, there are some differences worth noting.

The main difference is that anonymous interface implementations can have stateful (member variables), whereas lambda expressions cannot. Take a look at this screen:

public interface MyEventConsumer {

    public void consume(Object event);

}
Copy the code

This interface can be implemented using an anonymous interface implementation, as follows:

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

The anonymous MyEventConsumer implementation can have its own internal state. Check out this redesign:

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

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

Lambda expressions cannot have such fields. Thus, lambda expressions are said to be stateless.

Lambda type inference

Prior to Java 8, when implementing an anonymous interface, you had to specify the interface to implement. The following is an example of an anonymous interface implementation at the beginning of this article:

stateOwner.addStateListener(new StateChangeListener() {

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

Using lambda expressions, you can usually infer the type from the surrounding code. For example, you can infer the interface type of the parameter from the method declaration of the addStateListener() method (a single method on the StateChangeListener interface). This is called type reasoning. The compiler inferences the type of the parameter by looking for it elsewhere — in this case, the method definition. The following is an example at the beginning of this article showing that the StateChangeListener interface is not mentioned in a lambda expression:

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

In lambda expressions, parameter types can often be inferred as well. In the example above, the compiler can infer its type from the onStateChange() method declaration. Therefore, the types of the oldState and newState parameters are inferred from the method declaration of the onStateChange() method.

Lambda parameter

Since Java lambda expressions are really methods, lambda expressions can take arguments just like methods. The (oldState, newState) section of the lambda expression shown earlier specifies the arguments to the lambda expression. These parameters must match the method’s parameters on a single method interface. In this case, these parameters must be with StateChangeListener interface theonStateChangeonStateChange () method of parameter matching:

public void onStateChange(State oldState, State newState);
Copy the code

The number of arguments in a lambda expression and method must at least match.

Second, if you specify any parameter types in a lambda expression, those types must also match. I haven’t shown you how to place types on lambda expression parameters (shown later in this article), but in many cases, you don’t need them.

Zero parameters

If the way you match a lambda expression to a lambda expression takes no arguments, you can write a lambda expression like this:

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

Notice that there is nothing between the parentheses. This is to show that lambda takes no arguments.

A parameter

If you take a method that matches a Java lambda expression with an argument by taking one argument, you can write a lambda expression like this:

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

Note that the parameters are listed in parentheses.

You can also omit the parentheses when a lambda expression takes a single argument, as follows:

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

Multiple parameters

If your method of matching a Java lambda expression to a Java lambda expression requires multiple arguments, you need to list the arguments in parentheses. Here’s what it looks like in Java code:

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

You can omit the parentheses only if the method takes a single argument.

The parameter types

If the compiler cannot infer the parameter type from the lambda matching function interface method, it may sometimes be necessary to specify the parameter type for the lambda expression. Don’t worry, the compiler will tell you when it happens. Here are examples of Java Lambda parameter types:

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

As you can see, the type of the CAR parameter (CAR) is written in front of the parameter name itself, just as you would declare the parameter in a method elsewhere or implement the interface anonymously.

The var parameter type of Java 11

In Java 11, you can use the var keyword as a parameter type. The var keyword was introduced in Java 10 as local variable type inference. Var from Java 11 can also be used with lambda parameter types. Here is an example of using the Java var keyword as a parameter type in a lambda expression:

Function<String, String> toLowerCase = (var input) -> input.toLowerCase();
Copy the code

Parameter types declared using the var keyword above are inferred to be of type String, because the generic type of the variable’s type declaration is set to Function

, which means that Function’s parameter type and return type are String.
,>

The Lambda function body

The body of a lambda expression, and therefore the body of the function/method it represents, is specified to the -> right of the lambda declaration:

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

If your lambda expression needs to consist of multiple lines, you can include the body of the lambda function in {} parentheses, which Java also needs when declaring methods elsewhere. Here’s an example:

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

Returns a value from a Lambda expression

You can return values from Java lambda expressions just as you would from methods. You simply add the return statement to the body of the lambda function, as follows:

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

If all your lambda expression does is evaluate and return the return value, you can specify the return value in a shorter way. Instead:

(a1, a2) -> { return a1 > a2; }
Copy the code

You can write:

(a1, A2) -> A1 > A2;Copy the code

The compiler then calculates that the expression A1 > a2 is the return value of the lambda expression (hence the name of the lambda expression — because the expression returns some kind of value).

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 any other object. Here’s an example:

public interface MyComparator {

    public boolean compare(int a1, int a2);

}
Copy the code
MyComparator myComparator = (a1, a2) -> a1 > a2;

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

The first code block shows the interface implemented by the lambda expression. The second code block shows the definition of a lambda expression, how it is assigned to a variable, and finally how it is invoked by calling the interface methods implemented by the lambda expression.

Variable to capture

In some cases, Java lambda expressions can access variables declared outside the body of a lambda function. I have a video version of this section here:

Java Lambdas can capture the following types of variables:

  • A local variable
  • The instance variables
  • A static variable

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

Local variable capture

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

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

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

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

Currently, this lambda expression refers only to the argument values (chars) passed to it. But we can change that. Here is an updated version of the String variable that references the external declaration of the lambda function:

String myString = "Test";

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

As you can see, the lambda body now refers to the local variable myString declared outside the lambda body. This is only possible if the variable is “validly final”, and only if the variable is “validly final”, meaning that it does not change its value after allocation. If the value of the myString variable subsequently changes, the compiler complains about references to it within the lambda body.

Instance variable capture

Lambda expressions can also capture instance variables in the object that created the lambda. Here is an example that shows:

public class EventConsumerImpl {private String name = "MyConsumer"; Public void Attach (MyEventProducer eventProducer){eventProducer. Listen (e -> {system.out.println (this.name); }); }}Copy the code

Notice the reference to this.name in the lambda body. This will capture the closed EventConsumerImpl object name instance variable. You can even change the value of an instance variable after it has been captured — that value will be reflected in the lambda.

The semantics of this are actually one of the areas where Java lambdas differ from anonymous implementations of interfaces. Anonymous interface implementations can have their own instance variables, which are referenced by this reference. However, lambda cannot have its own instance variable, so this always points to closed objects.

Note: The design of the above event consumers is not particularly elegant. I did this to be able to illustrate instance variable capture.

Static variable capture

Java lambda expressions can also capture static variables. This is not surprising, since static variables can be accessed anywhere in a Java application, provided that static variables are accessible (packaged scoped or public).

Here is an example class that creates a lambda that refers to a static variable within the body of the lambda:

public class EventConsumerImpl { private static String someStaticVar = "Some text"; public void attach(MyEventProducer eventProducer){ eventProducer.listen(e -> { System.out.println(someStaticVar); }); }}Copy the code

The value of a static variable is also allowed to change after a lambda capture.

Again, the above class design is a bit ridiculous. Don’t overthink it. This class is mainly used to show you that a lambda can access static variables.

Method reference as Lambdas

If all your lambda expression does is call another method, with arguments passed to the lambda, the Java Lambda implementation provides a shorter way to express method calls. First, here’s an example of a single-function interface:

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

Here is an example of creating a Java lambda instance that implements the MyPrinter interface:

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

Since the lambda body consists of only a single statement, we can actually omit the included {} parentheses. Also, since the lambda method has only one argument, we can omit the parentheses () around the argument. The resulting lambda statement reads as follows:

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

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

MyPrinter myPrinter = System.out::println;
Copy the code

Note the double colon ‘::’. These signals tell the Java compiler that this is a method reference. The method referenced follows the double colon. Any class or object that has a reference method appears before the double colon.

You can refer to the following types of methods:

  • A static method
  • The instance method of the argument object
  • Instance methods
  • builders

The following sections cover each type of method reference.

Static method reference

The easiest method to refer to is the static method. Here is the first example of a single function interface:

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

This is a static method and we want to create a method reference:

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

Finally, here is a Java lambda expression that references a static method:

Finder finder = MyClass::doFind;
Copy the code

Because the arguments to finder.find () and the myClass.dofind () methods match, you can 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 a lambda. Imagine a single function interface like this:

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

This interface is intended to represent the ability to search S1 for components that appear in S2. Here is an example Of a Java lambda expression that calls String.indexof () for a search:

Finder finder = String::indexOf;
Copy the code

This corresponds to the definition of lambda:

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

Notice how the quick version references individual methods. The Java compiler will try to match the referenced method to the first parameter type and use the second parameter type as the argument to the referenced method.

Example method reference

Third, instance methods can also be referenced from a lambda definition. First, let’s look at a single method interface definition:

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

This interface represents a component that can “deserialize” String to int.

Now look at this StringConverter class:

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

The ‘convertToInt()’ method has the same signature as the ‘deserializer’ and ‘deserialize()’ method’s ‘deserialize()’ method. So we can create an instance of ‘stringConverter’ and reference its’ convertToInt() ‘method from a Java lambda expression, as follows:

StringConverter stringConverter = new StringConverter(); Deserializer des = stringConverter::convertToInt;Copy the code

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

Constructor reference

Finally, you can reference the constructor of the class. You do this by writing the class name, then ::new, like this:

MyClass::new
Copy the code

Also to see how constructors can be used as lambda expressions, take a look at this interface definition:

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

The create() method of this interface matches the signature of a constructor in the String class. Therefore, this constructor can be used as a lambda. Here’s an example of what it looks like:

Factory factory = String::new;
Copy the code

This is equivalent to this Java lambda expression:

Factory factory = chars -> new String(chars);
Copy the code