Method references
Often a lambda just calls one existing method and passes its parameter to it: s -> s.toUpperCase(), x -> IO.println(x). Such a lambda can be written shorter — as a method reference with the :: operator.
The first method reference
To the left of :: goes a class or an object, to the right the method name without parentheses. Both lines below do the same thing:
void main() {
List<String> names = List.of("Anna", "Oleg");
names.forEach(name -> IO.println(name));
names.forEach(IO::println);
}Output:
Anna
Oleg
Anna
OlegThe reference IO::println means "call IO.println() with each element". Java fills in the arguments by itself. A method reference is not a call: the method runs later, when forEach() calls it.
A static method
A reference of the form ClassName::staticMethod passes the lambda's parameters to a static method. This is handy for turning strings into numbers:
void main() {
Function<String, Integer> parse = Integer::parseInt;
BinaryOperator<Integer> add = Integer::sum;
IO.println(parse.apply("42") + 1);
IO.println(add.apply(2, 3));
}Output:
43
5A method of an arbitrary object
The notation String::toUpperCase looks like a static one but works differently: the lambda's first parameter becomes the object on which the method is called. It is equivalent to s -> s.toUpperCase():
void main() {
Function<String, String> upper = String::toUpperCase;
Function<String, Integer> length = String::length;
IO.println(upper.apply("hello"));
IO.println(length.apply("hello"));
}Output:
HELLO
5This is exactly how the comparators from the chapter on collections work: Comparator.comparing(Product::price) calls price() on each product.
A method of a particular object
If a variable stands on the left, the method is called on that object, and the lambda's parameter becomes the argument:
void main() {
List<String> cart = new ArrayList<>();
Consumer<String> addToCart = cart::add;
addToCart.accept("tea");
addToCart.accept("cookies");
IO.println(cart);
}Output:
[tea, cookies]A constructor reference
ClassName::new is a reference to a constructor. It creates a new object from the lambda's parameters:
record User(String name) {}
void main() {
Function<String, User> create = User::new;
Supplier<List<String>> newList = ArrayList::new;
IO.println(create.apply("Anna"));
IO.println(newList.get());
}Output:
User[name=Anna]
[]Common mistake
A method reference fits only if the method's parameters and return type match the functional interface. For example, String::length returns a number, while Predicate expects a boolean:
void main() {
Predicate<String> check = String::length;
}Output:
Main.java:2: error: incompatible types: bad return type in method reference
Predicate<String> check = String::length;
^
int cannot be converted to booleanIf the method does not fit directly, write an ordinary lambda: s -> s.length() > 3. In general, a method reference is not an obligation but a shortcut: pick whichever form reads more easily.