“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