Lambda expressions and method references (Java 8, JSR 335) are short ways to write an object that implements a functional interface: an interface with one abstract method. They have no type of their own; the interface comes from the context (JLS 15.13 and 15.27).
Lambda syntax
LambdaExpression:
LambdaParameters -> LambdaBody
LambdaParameters:
( [LambdaParameterList] )
Identifier
LambdaParameterList:
LambdaParameter {, LambdaParameter}
Identifier {, Identifier}
LambdaParameter:
{VariableModifier} LambdaParameterType VariableDeclaratorId
VariableArityParameter
LambdaParameterType:
UnannType | var
LambdaBody:
Expression | Block
| Form | Example |
|---|---|
| No parameters | () -> 42 |
| One inferred parameter, no parentheses | s -> s.length() |
| Several inferred parameters | (a, b) -> a + b |
| Declared types | (int a, int b) -> a + b |
var, which allows annotations and final (Java 11) | (final var a, @NonNull var b) -> a + b |
| Unnamed parameter (Java 22) | (k, _) -> k.toUpperCase() |
| Block body | (x) -> { log(x); return x * 2; } |
| Block body, no result | () -> { System.out.println("run"); } |
In one lambda, the parameters must be all inferred, all declared or all
var. A modifier or annotation needs a declared type or var.
Target types
A lambda may only appear in an assignment context, an invocation context or a casting
context, where a functional interface type is expected; anywhere else, such as
var f = x -> x; or (x -> x).apply(1), it is an error. The
interface’s single abstract method gives the lambda’s function type: its
parameter types, return type and checked exceptions.
Function<String, Integer> len = s -> s.length(); // assignment context
list.removeIf(s -> s.isBlank()); // invocation context: Predicate<String>
Object o = (Runnable) () -> System.out.println("hi"); // casting context
Callable<String> c = () -> Files.readString(path); // Callable.call may throw Exception
The lambda is compatible with the function type when:
- it has the same number of parameters, and declared parameter types match exactly;
- for a
voidfunction type, its body is a statement expression (such as a call) or a block with noreturnvalue; - for a value-returning function type, its body is an expression of a compatible
type or a block whose every
returngives one and which cannot complete normally; - its body throws no checked exceptions beyond those of the function type.
An expression body that is a method call, such as () -> list.add(x), is
compatible with both void and value function types.
Names, this and capture
- A lambda body is in the same scope as the code around it. Its parameters and
local variables cannot reuse the names of the enclosing method’s local variables,
and
thisandsupermean the same as outside it: the enclosing instance, not the lambda object. (In an anonymous class, they mean the anonymous object.) - A lambda may use local variables and parameters of the enclosing code only if
they are
finalor effectively final; their values are captured. Fields can be read and written freely, because they are reached throughthis. breakandcontinuecannot leave a lambda body, andreturnreturns from the lambda, not from the enclosing method.
class Counter {
int total; // a field: may be changed from a lambda
void addAll(List<Integer> xs) {
int base = 10; // effectively final: may be captured
xs.forEach(x -> total += x + base);
// base++; // would make the capture above an error
}
}
Evaluation
Evaluating a lambda expression produces an object that implements the target
interface; the body runs only when the interface method is called. The language does
not promise a new object each time: a lambda that captures nothing may be the same
object on every evaluation, so lambdas should not be compared with == or
used as locks. A lambda is serialisable only if its target type is (for example by
casting to Runnable & Serializable), and serialising lambdas is
discouraged.
javac compiles the body into a private method of the enclosing class
and creates the object at run time through the invokedynamic instruction
and LambdaMetafactory.
Method references
MethodReference:
ExpressionName :: [TypeArguments] Identifier
Primary :: [TypeArguments] Identifier
ReferenceType :: [TypeArguments] Identifier
super :: [TypeArguments] Identifier
TypeName . super :: [TypeArguments] Identifier
ClassType :: [TypeArguments] new
ArrayType :: new
| Kind | Example | Same as |
|---|---|---|
| Static method | Integer::parseInt | s -> Integer.parseInt(s) |
| Instance method of a given object | System.out::println | x -> System.out.println(x), with System.out evaluated once, at creation |
| Instance method of an arbitrary object of a type | String::length | s -> s.length(): the first parameter is the receiver |
| Superclass or superinterface method | super::toString, Iface.super::m | () -> super.toString() |
| Constructor | ArrayList::new | () -> new ArrayList<>(), or with arguments, depending on the target |
| Array constructor | int[]::new | n -> new int[n] |
- Which overload is meant is decided by the target’s function type:
Function<String, Integer> f = Integer::valueOf;picksvalueOf(String). - For
expr::name, the expression is evaluated when the reference is evaluated, not when it is called; if it isnull, aNullPointerExceptionis thrown at once. Type::namecan be ambiguous when the type has both a static method and an instance method that fit; that is an error.- A constructor reference to an inner class uses the enclosing
this; a reference to a generic class’s constructor may use the diamond implicitly (HashMap::new).
List<String> words = List.of("pear", "fig", "apple");
List<Integer> lengths = words.stream().map(String::length).toList(); // [4, 3, 5]
String[] array = words.toArray(String[]::new);
Map<Integer, List<String>> byLength =
words.stream().collect(Collectors.groupingBy(String::length, TreeMap::new, Collectors.toList()));
words.forEach(System.out::println);
Lambdas and overloading
When a method is overloaded on different functional interfaces, an implicitly typed lambda does not help choose between them, because its parameter types are not known until a method is chosen (JLS 15.12.2.2). An explicitly typed lambda (one with declared parameter types, or no parameters), or a cast, settles it:
static void m(Function<String, Integer> f) { }
static void m(ToIntFunction<String> f) { }
m(s -> s.length()); // error: ambiguous
m((String s) -> s.length()); // ToIntFunction: an int result is more specific
m((Function<String, Integer>) s -> s.length()); // forced to Function