- Understand the four types of method references
- Learn when to use method references vs lambdas
- Master the :: operator syntax
- Apply method references in common patterns
Method References
Lambdas made code concise. But sometimes you're just calling an existing method—x -> System.out.println(x). Method references make this even cleaner: System.out::println.
They're shorthand when the lambda only calls one method.
What are Method References?
A method reference is a way to refer to a method without executing it. It uses the :: operator:
// Lambda
Consumer<String> printer1 = s -> System.out.println(s);
// Method reference (equivalent)
Consumer<String> printer2 = System.out::println;
// Both do the same thing
printer1.accept("Hello"); // prints "Hello"
printer2.accept("Hello"); // prints "Hello"
Four Types of Method References
1. Reference to a Static Method
Syntax: ClassName::staticMethodName
// Lambda
Function<String, Integer> parser1 = s -> Integer.parseInt(s);
// Method reference
Function<String, Integer> parser2 = Integer::parseInt;
// Usage
Integer num = parser2.apply("42"); // 42
More examples:
// Math.abs
Function<Integer, Integer> abs = Math::abs;
// String.valueOf
Function<Object, String> toString = String::valueOf;
// Arrays.sort
Consumer<int[]> sorter = Arrays::sort;
2. Reference to an Instance Method of a Particular Object
Syntax: objectReference::instanceMethodName
String prefix = "Hello, ";
// Lambda
Function<String, String> greeter1 = name -> prefix.concat(name);
// Method reference
Function<String, String> greeter2 = prefix::concat;
// Usage
greeter2.apply("World"); // "Hello, World"
More examples:
List<String> names = new ArrayList<>();
// Lambda
Consumer<String> adder1 = name -> names.add(name);
// Method reference
Consumer<String> adder2 = names::add;
// With System.out
Consumer<String> printer = System.out::println;
3. Reference to an Instance Method of an Arbitrary Object of a Particular Type
Syntax: ClassName::instanceMethodName
This one is tricky - the first parameter becomes the object on which the method is called:
// Lambda: s is the object, length() is called on it
Function<String, Integer> lengthFn1 = s -> s.length();
// Method reference: String::length means "call length() on the String"
Function<String, Integer> lengthFn2 = String::length;
// Usage
lengthFn2.apply("hello"); // 5
For comparison methods:
// Lambda: first param is the object, second is the argument
Comparator<String> comp1 = (s1, s2) -> s1.compareToIgnoreCase(s2);
// Method reference
Comparator<String> comp2 = String::compareToIgnoreCase;
// Usage
comp2.compare("Hello", "hello"); // 0
4. Reference to a Constructor
Syntax: ClassName::new
// Lambda
Supplier<ArrayList<String>> listFactory1 = () -> new ArrayList<>();
// Constructor reference
Supplier<ArrayList<String>> listFactory2 = ArrayList::new;
// Usage
List<String> list = listFactory2.get();
With parameters:
// Lambda
Function<String, StringBuilder> sbFactory1 = s -> new StringBuilder(s);
// Constructor reference
Function<String, StringBuilder> sbFactory2 = StringBuilder::new;
// Usage
StringBuilder sb = sbFactory2.apply("Hello");
Method Reference Summary Table
| Type | Syntax | Lambda Equivalent |
|---|---|---|
| Static method | ClassName::staticMethod |
x -> ClassName.staticMethod(x) |
| Instance method (specific object) | object::instanceMethod |
x -> object.instanceMethod(x) |
| Instance method (arbitrary object) | ClassName::instanceMethod |
x -> x.instanceMethod() |
| Constructor | ClassName::new |
x -> new ClassName(x) |
When to Use Method References
Use Method References When:
- The lambda just calls a method with the same parameters:
// Perfect for method reference
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println); // Better!
names.sort((a, b) -> a.compareToIgnoreCase(b));
names.sort(String::compareToIgnoreCase); // Better!
- You want cleaner, more readable code:
// Clear intent: "convert each to uppercase"
list.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
Keep Lambdas When:
- You need to transform parameters:
// Need lambda - parameter order/transformation
BiFunction<String, String, String> fn = (a, b) -> a.substring(0, b.length());
- You're calling multiple methods:
// Need lambda - multiple operations
Consumer<String> process = s -> {
validate(s);
save(s);
};
- The method name would be confusing:
// Lambda is clearer here
Predicate<Integer> isEven = n -> n % 2 == 0;
// vs some hypothetical MathUtils::isEven
Common Patterns
Collection Operations
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Printing
names.forEach(System.out::println);
// Sorting
names.sort(String::compareTo);
names.sort(String::compareToIgnoreCase);
// Mapping
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// Filtering with method on object
List<String> nonEmpty = strings.stream()
.filter(s -> !s.isEmpty()) // Can't use method ref here
.collect(Collectors.toList());
Creating Objects
// Create stream of new objects
List<StringBuilder> builders = names.stream()
.map(StringBuilder::new)
.collect(Collectors.toList());
// With Supplier
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> newList = listSupplier.get();
With Comparators
// Sort by property
people.sort(Comparator.comparing(Person::getName));
people.sort(Comparator.comparing(Person::getAge).reversed());
// Multiple criteria
people.sort(Comparator
.comparing(Person::getLastName)
.thenComparing(Person::getFirstName));
Method References with Generics
// Works with generic types
Function<List<String>, Integer> sizeGetter = List::size;
BiFunction<List<String>, String, Boolean> containsChecker = List::contains;
// Constructor with generic
Supplier<List<String>> listFactory = ArrayList::new;
Common Mistakes
Trying to Reference Private Methods
public class Example {
private void helper() { }
public void demo() {
// This works - same class
Runnable r = this::helper;
}
}
class Other {
public void demo(Example e) {
// This fails - helper is private
// Runnable r = e::helper; // Compilation error!
}
}
Ambiguous Method References
// If multiple overloaded methods exist, context must resolve it
public static void process(String s) { }
public static void process(Integer i) { }
Consumer<String> c = Example::process; // OK - unambiguous
Consumer<Integer> c2 = Example::process; // OK - unambiguous
// Consumer<Object> c3 = Example::process; // Error - ambiguous
Method references are shorthand for lambdas that just call a method. Four types: static (Integer::parseInt), bound instance (object::method), unbound instance (String::length), constructor (ArrayList::new). Use when lambda simply delegates to existing method. Keep lambda when you need parameter transformation. Method references often improve readability.
