When a lambda does nothing except call an existing method, you can replace it with a method reference using ::. It’s shorter, and often reads almost like English: map(Order::id) — “map each order to its id”.
skus.forEach(s -> System.out.println(s)); // lambda
skus.forEach(System.out::println); // method reference — same thing
The four kinds
| Kind | Syntax | Equivalent lambda |
|---|---|---|
| Static method | ClassName::staticMethod | s -> MethodReferences.isValidSku(s) |
| Method of a particular object | object::method | s -> prefix.equals(s) |
| Method of an arbitrary object of a type | ClassName::instanceMethod | s -> s.toUpperCase() |
| Constructor | ClassName::new | n -> new Customer(n) |
1. Static method
Predicate<String> valid = MethodReferences::isValidSku;
2. Instance method of a particular (“bound”) object
String prefix = "SKU:";
Predicate<String> hasPrefix = prefix::equals; // always calls prefix.equals(...)
skus.forEach(System.out::println); // System.out is the object
3. Instance method of an arbitrary (“unbound”) object
Function<String, String> upper = String::toUpperCase; // the argument becomes 'this'
Function<Customer, String> greet = Customer::greeting;
BiFunction<String, String, Boolean> same = String::equalsIgnoreCase; // (a, b) -> a.equalsIgnoreCase(b)
This is the one that confuses people: String::toUpperCase takes a String and calls toUpperCase() on it. With two parameters, the first becomes the object and the second the argument.
4. Constructor
Function<String, Customer> create = Customer::new;
Supplier<List<String>> newList = ArrayList::new;
String[] array = list.stream().toArray(String[]::new);
When to use them
Use a method reference when it’s clearer than the lambda — usually when the method name says exactly what happens. Keep the lambda when you need to do anything extra (o -> o.amount() * 1.2), or when the reference would be ambiguous between overloaded methods.
Comparators read beautifully
customers.sort(Comparator.comparing(Customer::name));
orders.sort(Comparator.comparing(Order::customer)
.thenComparing(Order::amount)
.reversed());
The full example
// MethodReferences.java — the four kinds of method reference.
import java.util.*;
import java.util.function.*;
public class MethodReferences {
static boolean isValidSku(String s) {
return s.matches("[A-Z]{2}-\\d{3}");
}
record Customer(String name) {
String greeting() { return "Hello, " + name; }
}
public static void main(String[] args) {
List<String> skus = List.of("AX-200", "bad", "BX-310");
// 1. Static method: ClassName::staticMethod
Predicate<String> valid = MethodReferences::isValidSku; // s -> isValidSku(s)
System.out.println(skus.stream().filter(valid).toList()); // [AX-200, BX-310]
// 2. Instance method of a particular object: object::method
String prefix = "SKU:";
Predicate<String> hasPrefix = prefix::equals; // s -> prefix.equals(s)
System.out.println(hasPrefix.test("SKU:")); // true
skus.forEach(System.out::println); // s -> System.out.println(s)
// 3. Instance method of an arbitrary object of a type: ClassName::instanceMethod
Function<String, String> upper = String::toUpperCase; // s -> s.toUpperCase()
Function<Customer, String> greet = Customer::greeting; // c -> c.greeting()
System.out.println(upper.apply("acme") + " / " + greet.apply(new Customer("Globex")));
// 4. Constructor: ClassName::new
Function<String, Customer> create = Customer::new; // n -> new Customer(n)
Supplier<List<String>> newList = ArrayList::new; // () -> new ArrayList<>()
List<String> names = newList.get();
names.add(create.apply("Initech").name());
System.out.println(names); // [Initech]
// Comparator built from method references
List<Customer> customers = new ArrayList<>(List.of(new Customer("Zed"), new Customer("Amy")));
customers.sort(Comparator.comparing(Customer::name));
System.out.println(customers);
}
}
Try it yourself
Rewrite each lambda as a method reference:
Function<String, Integer> a = s -> Integer.parseInt(s);
Consumer<Object> b = o -> System.out.println(o);
Function<String, Integer> c = s -> s.length();
Supplier<Set<String>> d = () -> new HashSet<>();
Show a solution
Function<String, Integer> a = Integer::parseInt; // static
Consumer<Object> b = System.out::println; // bound
Function<String, Integer> c = String::length; // unbound
Supplier<Set<String>> d = HashSet::new; // constructor
Key takeaways
::turns an existing method or constructor into a function.- Four kinds: static, bound (
obj::m), unbound (Type::m) and constructor (Type::new). - With unbound references, the first argument becomes the object the method is called on.
- Prefer them when they read more clearly than the lambda.