Learning outcome: Replace suitable forwarding lambdas with method references, distinguish all four forms, and match a reference to a functional interface.
A lambda sometimes does nothing except call an existing method. A method reference expresses that delegation using ::. Like a lambda, it needs a compatible functional interface. It identifies behavior to invoke later; writing the reference does not by itself perform the call.
Four Forms
| Form | Reference | Equivalent lambda in a compatible context |
|---|---|---|
| Static method | Integer::parseInt |
text -> Integer.parseInt(text) |
| Instance method on a particular object | System.out::println |
text -> System.out.println(text) |
| Instance method on an object supplied as an argument | String::length |
text -> text.length() |
| Constructor | ArrayList<String>::new |
() -> new ArrayList<String>() |
The second form is often called a bound reference: its receiver is already selected. The third is unbound: the first argument supplies the receiver. These four forms are covered in Oracle's method reference tutorial.
A Complete Example
Save as MethodReferenceDemo.java:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.function.ToIntFunction;
public class MethodReferenceDemo {
public static void main(String[] args) {
Function<String, Integer> parse = Integer::parseInt;
Consumer<String> print = System.out::println;
ToIntFunction<String> length = String::length;
Supplier<List<String>> newList = ArrayList<String>::new;
List<String> names = newList.get();
names.addAll(Arrays.asList("Kabir", "Asha", "Meera"));
names.sort(String::compareToIgnoreCase);
print.accept("Parsed: " + parse.apply("84"));
print.accept("Length: " + length.applyAsInt("Asha"));
names.forEach(print);
}
}
Output:
Parsed: 84
Length: 4
Asha
Kabir
Meera
Function<String,Integer> expects one string and an Integer result; parseInt returns an int, which is boxed. ToIntFunction<String> returns a primitive int, so String::length fits without result boxing. Supplier<List<String>> has no arguments; it selects a no-argument constructor and supplies a new list on each call.
Understanding the Receiver
The comparator reference String::compareToIgnoreCase corresponds to (left, right) -> left.compareToIgnoreCase(right). The first comparator argument becomes the receiver, and the second is passed to the method.
By comparison, if String prefix = "CS";, the reference prefix::concat binds that particular prefix and takes only the suffix as its argument. A bound receiver must be non-null when the method reference is created. With an unbound reference, passing a null receiver later fails when the method is invoked.
In simple terms: In printer::print, the printer has already been chosen. In String::length, the string will arrive as an argument when the function is called.
Matching Inputs and Results
The compiler checks the parameter count, compatible parameter types, result, and checked exceptions against the functional interface. An overloaded method is selected using that target type; the name alone is not enough.
For example, Integer::parseInt works as a ToIntFunction<String>, but not as a Supplier<Integer> because parsing needs a string argument. A constructor taking a name could be used as a Function<String,Student> through Student::new; its result is the newly created student.
Array creation also supports constructor references. String[]::new can target IntFunction<String[]>: its input is the desired length and its output is a new string array. It does not fill the array with non-null strings.
When a Lambda Is Clearer
Use a reference when a lambda simply forwards its arguments to an existing operation. Keep a lambda when it transforms arguments, supplies extra values, or combines operations.
| Requirement | Suitable expression |
|---|---|
| Parse an integer | Integer::parseInt |
| Trim, then parse | text -> Integer.parseInt(text.trim()) |
| Print a supplied string | System.out::println |
| Print a label and value | mark -> System.out.println("Mark: " + mark) |
| Keep passing marks | mark -> mark >= 40 |
Neither notation changes whether an API executes sequentially or concurrently. Choose the expression that makes the behavior easiest to read.
Practice
- Rewrite
name -> name.toUpperCase(java.util.Locale.ROOT)as a named helper method plus a reference. Explain why the locale argument prevents the simple referenceString::toUpperCasefrom expressing exactly the same choice. - Create a
Student(String name)constructor and use aFunction<String,Student>to construct two students. - Use
String[]::newto allocate an array of a requested size.
Quick Check
1. Does Integer::parseInt immediately parse a value?
No. A compatible functional interface receives the reference. Parsing happens when its method is invoked with text.
2. How many inputs does String::compareToIgnoreCase receive as a Comparator?
Two strings. The first is the receiver and the second is the method argument.
3. Can every lambda be replaced by a method reference?
No. Additional calculations, conditions, or argument transformations often require a lambda or a separate helper method.
Summary
Method references name existing behavior. Determine whether the receiver is bound or supplied as an argument, then match the reference to the functional contract. Continue with Stream API operations.