Lambdas and Method References
|
This section documents the current Java release line, with Java 25 LTS as the reference point (no specific patch version is pinned), as published at the Java developer portal, the Java Tutorials, and the Java SE API specification — which are the references these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A lambda expression is an unnamed function used as a value, written wherever an instance of a functional interface (an interface with a single abstract method) is expected. This page follows dev.java "Lambda Expressions" and the Java Tutorials "Lambda Expressions" lesson.
Lambda Syntax and Target Typing
A lambda has a parameter list, an arrow ->, and a body that is either one expression or a
brace-enclosed block of statements.
import java.util.function.*;
Supplier<String> greet = () -> "hello"; // no parameters
Function<Integer, Integer> square = n -> n * n; // one parameter, expression body
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b; // two parameters
Predicate<String> isBlank = s -> s.strip().isEmpty();
Runnable task = () -> { // block body, no value produced
System.out.println("working");
System.out.println("done");
};
Function<Integer, String> classify = n -> { // block body, explicit return
if (n < 0) {
return "negative";
}
return n == 0 ? "zero" : "positive";
};
A lambda has no type of its own. The compiler reads the target type from context — the variable being
assigned, the parameter being passed, the value being returned — and checks the lambda against that
interface’s single abstract method. The very same (a, b) -> a + b is a
BiFunction,
an
IntBinaryOperator,
or a custom interface depending on where it appears. With no functional-interface target it does not
compile:
Object o = () -> "nope"; // does NOT compile: Object is not a functional interface
The
java.util.function
package supplies the common shapes (Function, Predicate, Supplier, Consumer, BiFunction, and
the primitive specialisations); prefer them over hand-rolled interfaces.
Parameter types and var
Parameter types are normally inferred and left off. You may write them explicitly, or use var for
every parameter (all-or-nothing) so an annotation can be attached:
BiFunction<Integer, Integer, Integer> a = (Integer x, Integer y) -> x + y; // explicit types
BiFunction<Integer, Integer, Integer> b = (x, y) -> x + y; // inferred
BiFunction<Integer, Integer, Integer> c = (var x, var y) -> x + y; // var form
You cannot mix the forms: (var x, y) and (var x, Integer y) are both errors. A single inferred
parameter may drop its parentheses (n -> n * n); the var and explicit forms always keep them. The
grammar is defined in
JLS 15.27.
Capturing Variables and this
A lambda may use local variables from the enclosing scope only if they are effectively final — assigned exactly once. That lets the captured value be copied safely.
import java.util.function.Function;
static Function<String, String> prefixer(String prefix) {
int calls = 0;
// return s -> { calls++; return prefix + s; }; // does NOT compile: calls is reassigned
return s -> prefix + s; // OK: prefix is effectively final
}
To accumulate state across calls, capture a reference to a mutable holder — an array, an
AtomicLong,
a field — rather than reassigning a local:
var count = new java.util.concurrent.atomic.AtomicLong();
Runnable tick = () -> count.incrementAndGet(); // the reference is final; the object mutates
tick.run();
tick.run();
System.out.println(count.get()); // 2
Inside a lambda, this refers to the enclosing instance — the object whose method is running — just
as it would in the surrounding code. A lambda opens no new scope: its parameters and locals share the
enclosing method’s namespace and cannot shadow a name already in scope.
class Widget {
private String name = "widget";
Runnable namePrinter() {
return () -> System.out.println(this.name); // this == the Widget; prints "widget"
}
}
Method References
When a lambda does nothing but call one existing method, a method reference written with :: names
that method directly. See
Method References. There are
four kinds:
| Kind | Syntax | Equivalent lambda | Example |
|---|---|---|---|
Static method |
|
|
|
Bound instance method |
|
|
|
Unbound instance method |
|
|
|
Constructor |
|
|
|
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.*;
List<String> words = List.of("gamma", "alpha", "beta");
words.stream().map(String::toUpperCase).forEach(System.out::println); // unbound, then bound
List<Integer> lengths = words.stream().map(String::length).toList();
int total = Stream.of("1", "2", "3").mapToInt(Integer::parseInt).sum(); // static; total == 6
Supplier<List<String>> freshList = ArrayList::new; // constructor reference
List<String> sorted = words.stream()
.collect(Collectors.toCollection(TreeSet::new))
.stream()
.toList();
The unbound form String::toUpperCase takes the receiver as its first argument, so it matches a
Function<String, String>;
String::compareTo matches a
Comparator<String>.
Lambda vs. Anonymous Class
A lambda is not merely terser syntax for an anonymous class that implements a functional interface. Three concrete differences:
-
No new scope. A lambda body sees the enclosing method’s locals directly and cannot re-declare a name already visible. An anonymous class opens a fresh scope and may shadow.
-
this. In a lambda,thisis the enclosing instance. In an anonymous class,thisis the anonymous object; the enclosing instance isEnclosing.this. -
No separate class file. The compiler renders a lambda through an
invokedynamiccall site rather than a syntheticWidget$1.class, and may reuse a single instance for a stateless lambda. An anonymous class always produces its own class file and a fresh object per evaluation.
int factor = 3;
Function<Integer, Integer> viaLambda = n -> n * factor; // sees the enclosing 'factor'
Function<Integer, Integer> viaAnon = new Function<>() {
@Override
public Integer apply(Integer n) {
int factor = 10; // legal: the anonymous class has its own scope and shadows
return n * factor;
}
};
Use a lambda for a one-method behaviour with no state of its own; use an anonymous class when you need multiple methods, instance fields, or to extend a class — see Nested and Anonymous Classes.
See Also
-
Interfaces — functional interfaces and
@FunctionalInterface. -
Nested and Anonymous Classes — the heavier alternative and how
thisand scope differ. -
Functional Programming — the
java.util.functioncatalogue and composition. -
Streams and Collectors — the API where lambdas and method references are used most.