The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Lambdas and streams
Lambda expressions and functional interfaces
When does the code in a lambda expression run?
A lambda expression is syntax that lets you store "an action to run later" in a variable like a value and pass it to another method. The code inside a lambda expression does not run on the line where you write it. It runs only when the method that receives it calls it.
Checked against real execution · Checked by running 36 combinations on JDK 21
Concepts in this lab
- Lambda expression
- This is a syntax, introduced in Java 8, for writing a function with no name in a compact way. It has the form (parameters) -> body, and it is used as the implementation of a functional interface, which has only one abstract method. It lets you pass behavior (code) to another method as if it were a value.
- Functional interface
- This is an interface with only one abstract method (a redeclared method of Object such as equals is not counted, and any number of default and static methods is allowed), and it is a type to which a lambda expression or a method reference can be assigned. The java.util.function package predefines types such as Function (takes an input and returns a result), Supplier (supplies a value), Consumer (consumes a value), and Predicate (tests a condition).
- Method reference
- This is a syntax that refers to an existing method by name in place of a lambda. You write it by joining a class (or an object) and a method name with ::, as in String::length or System.out::println. It is a shorter way to write a lambda that does nothing but call one method.
- Differences between anonymous classes and lambda expressions
- An anonymous class is syntax that implements an interface or a class right where it is used and creates an object without giving the class a name, and it is written in the form new InterfaceName() { method implementations }. A lambda expression is syntax that does the same thing more briefly, in the form (parameters) -> statement, but only for a functional interface, which has exactly one abstract method. When you implement a functional interface with a simple body that does not use this, the result of a call is the same whichever of the two you use. However, an anonymous class can also implement an interface that has several methods, and it can have fields, and this inside it refers to the object of the anonymous class itself. this inside a lambda expression refers to the enclosing object in which the lambda expression is written.
Try these
- Default settings — Nothing happens on the line that creates the lambda expression, and the body runs each time run calls it.
- Receiving it without calling it — The lambda expression is created and passed, but the body never runs.
- Writing it as an anonymous class — The code gets longer, but the result is the same as with the lambda expression.
- Writing it as a method reference — It refers to an existing method by name. The result is the same.
- Passing a Supplier and calling get later — The work of creating a value is put off and runs when the value is needed.
- Predicate — It passes a condition check. true or false comes back.
- Consumer — It takes a value, only uses it, and returns nothing.
Running with the default settings
- Function<String, Integer> f = s -> length(s): This creates an object that holds the action written to the right of the arrow token (->) and stores it in the variable f. The length method is not called yet. Nothing is printed.
- System.out.println("after creation"): "after creation" is printed. Execution has already passed the line that creates the action, but "body runs" has not been printed even once.
- run(f): The action held in the variable f is passed to the run method. An action is passed in the same way as a number or a string. Passing it does not make it run.
- System.out.println("run starts"): Execution enters run, and "run starts" is printed.
- f.apply("hello"): This is where the action finally runs. The length method is called, and "body runs: length(hello)" is printed. The returned value 5 is printed as "result 5".
- f.apply("hi"): The same action runs one more time. The length method is called, and "body runs: length(hi)" is printed. The returned value 2 is printed as "result 2".
- System.out.println("run ends"): "run ends" is printed. "body runs" was printed 2 times in all.
Result of this run
- Nothing ran on the line that creates the action. The body ran each time the apply method was called inside run. The code inside a lambda expression runs when it is called, not when it is created.
- Function<T, R> is an action that takes one value of type T and returns a value of type R, and it is called with apply. It is used to turn a value into another value (conversion).
- The result is the same whether you write the action as an anonymous class, a lambda expression, or a method reference.
Code
class Demo {
static int length(String s) { // what the action to pass does
System.out.println("body runs: length(" + s + ")");
return s.length();
}
static void run(Function<String, Integer> f) { // the method that receives the action and calls it
System.out.println("run starts");
System.out.println("result " + f.apply("hello"));
System.out.println("result " + f.apply("hi"));
System.out.println("run ends");
}
public static void main(String[] args) {
Function<String, Integer> f = s -> length(s); // creates the action (it does not run yet)
System.out.println("after creation");
run(f); // passes the action like a value
}
}
Assumptions of this model
The output order, the number of times the body ran, and the returned values of the 36 combinations were checked against the result of running code with the same structure on JDK 21 on this PC. The name of the method that each interface calls, the number of values it takes, and the type it returns were also checked against values read from the real interfaces. Saying that an anonymous class, a lambda expression, and a method reference are the same means that the results seen in this lab (the output and the returned values) are the same. How the object is created internally and what this refers to differ among them. When a lambda expression uses a local variable of the enclosing scope, it can use only a variable whose value does not change, and this rule is explained in the "Inner classes and anonymous classes" lab.
Previous lab: Sorting and Comparator · Next lab: Lazy evaluation in streams