7.3 Method References: Static, Bound, Unbound, and Constructor References
Key Takeaways
A method reference (::) is a compact syntax for a lambda that delegates directly to an existing method or constructor; it requires a target functional interface context and cannot be assigned to var or Object.
Static method references (ClassName::staticMethod) map parameters of the functional interface directly to the static method's argument list in order.
Bound instance method references (instanceRef::instanceMethod) bind to a specific pre-existing object evaluated when the reference is created; the object is not supplied as a functional interface parameter.
Unbound instance method references (ClassName::instanceMethod) use the first parameter of the functional interface as the target receiver on which the instance method is invoked.
Constructor references (
ClassName::newandType[]::new) invoke constructors; array constructor references take an integer size parameter and matchIntFunction<Type[]>.
7.3 Method References: Static, Bound, Unbound, and Constructor References
Method references provide a compact, highly readable alternative to lambda expressions when an expression does nothing more than invoke an existing method or constructor by name. Using the double colon separator (::), method references allow developers to treat existing methods as first-class functional targets. For the 1Z0-829 exam, you must clearly distinguish between the four categories of method references, understand how the compiler differentiates bound from unbound references, and navigate constructor reference mechanics.
Syntax and Target Typing
A method reference consists of a target reference or type name, the double colon operator (::), and the method identifier or the new keyword:
Like lambda expressions, method references have no intrinsic standalone type. The compiler infers whether a method reference is compatible with a given target based on the expected functional interface's abstract method descriptor:
// Legal: compiler infers Consumer<String> from assignment context
Consumer<String> printer = System.out::println;
// COMPILE ERROR: Cannot infer type for method reference using var
// var ref = System.out::println;
// COMPILE ERROR: Object is not a functional interface
// Object obj = System.out::println;
// Legal: explicit cast establishes target functional interface context
Object obj = (Consumer<String>) System.out::println;
The Four Categories of Method References
The Java Language Specification defines four distinct forms of method references based on how the target method is invoked and whether an instance receiver is bound:
| Category | Syntax | Equivalent Lambda Expression | Example |
|---|---|---|---|
| 1. Static Method Reference | ClassName::staticMethod | (args) -> ClassName.staticMethod(args) | Integer::parseInt, Math::max |
| 2. Bound Instance Reference | instanceRef::instanceMethod | (args) -> instanceRef.instanceMethod(args) | System.out::println, "Java"::concat |
| 3. Unbound Instance Reference | ClassName::instanceMethod | (instance, args) -> instance.instanceMethod(args) | String::toLowerCase, String::concat |
| 4. Constructor Reference | ClassName::new / Type[]::new | (args) -> new ClassName(args) / size -> new Type[size] | ArrayList::new, String[]::new |
+--------------------------+
| Method Reference Types |
+--------------------------+
|
+--------------------+---------------+--------------------+--------------------+
| | | |
v v v v
[Static Reference] [Bound Instance] [Unbound Instance] [Constructor Reference]
ClassName::method instance::method ClassName::method ClassName::new / Type[]::new
args -> Class.m(args) args -> instance.m(args) (inst, args)->inst.m(args) args -> new Class(args)
1. Static Method References
Refers to a static method of a class. The parameters supplied to the functional interface are passed directly as the arguments to the static method in identical order:
// Static method taking 1 argument: Function<String, Integer>
Function<String, Integer> parser = Integer::parseInt; // s -> Integer.parseInt(s)
Integer val = parser.apply("123"); // 123
// Static method taking 2 arguments: BinaryOperator<Double>
BinaryOperator<Double> maxFinder = Math::max; // (a, b) -> Math.max(a, b)
Double greater = maxFinder.apply(10.5, 20.2); // 20.2
2. Bound Instance Method References
A bound instance method reference binds to a specific object instance that is evaluated when the method reference expression is created. The functional interface parameters do not include the target instance:
String prefix = "USER_";
// Bound reference: 'prefix' is evaluated now and captured
Function<String, String> tagger = prefix::concat; // s -> prefix.concat(s)
System.out.println(tagger.apply("42")); // "USER_42"
// Bound reference with zero arguments: Supplier<Integer>
Supplier<Integer> lenSupplier = prefix::length; // () -> prefix.length()
System.out.println(lenSupplier.get()); // 5
Warning
Null Evaluation Timing in Bound References
When creating a bound method reference (instanceRef::method), instanceRef is evaluated immediately at declaration time. If instanceRef is null, a NullPointerException is thrown instantly when the reference is defined, not when the functional interface method is subsequently invoked!
3. Unbound Instance Method References
An unbound instance method reference specifies a class name rather than an existing instance. The first parameter of the target functional interface serves as the receiver object on which the instance method is invoked. Any subsequent parameters map to the method's arguments:
// Single parameter becomes the invocation target: Function<String, Integer>
Function<String, Integer> lengthFunc = String::length; // s -> s.length()
System.out.println(lengthFunc.apply("OpenJDK")); // 7
// Unbound reference matching Predicate<String>
Predicate<String> blankChecker = String::isBlank; // s -> s.isBlank()
System.out.println(blankChecker.test(" ")); // true
// Two parameters: 1st is target, 2nd is method argument: BiPredicate<String, String>
BiPredicate<String, String> startsWithChecker = String::startsWith; // (s1, s2) -> s1.startsWith(s2)
System.out.println(startsWithChecker.test("Java 17", "Java")); // true
// Two parameters: 1st is target, 2nd is method argument: BiFunction<String, String, String>
BiFunction<String, String, String> concatFunc = String::concat; // (s1, s2) -> s1.concat(s2)
System.out.println(concatFunc.apply("Open", "JDK")); // "OpenJDK"
Comparing Bound vs. Unbound Syntax
Consider the method String.concat(String str):
- Bound:
"Hello"::concatbinds to"Hello". It accepts 1 parameter (String) and returnsString. Target:Function<String, String>. - Unbound:
String::concathas no bound instance. It requires 2 parameters (targetStringand argumentString) and returnsString. Target:BiFunction<String, String, String>.
Notice that both static method references and unbound instance method references use the syntax ClassName::methodName. The compiler resolves whether it is static or unbound by checking if methodName is static or instance, and comparing the arity of the target functional interface.
Constructor References
Constructor references instantiate new objects or arrays using the new keyword.
Class Constructor References (ClassName::new)
The compiler selects the appropriate constructor based on the functional interface's abstract method parameters:
// Zero arguments: Supplier<List<String>> invokes new ArrayList<>()
Supplier<List<String>> listFactory = ArrayList::new;
List<String> list = listFactory.get();
// One argument: Function<String, StringBuilder> invokes new StringBuilder(String)
Function<String, StringBuilder> sbFactory = StringBuilder::new;
StringBuilder sb = sbFactory.apply("Initial");
// Two arguments: BiFunction<String, Integer, CustomRecord>
record Employee(String name, int id) {}
BiFunction<String, Integer, Employee> empFactory = Employee::new;
Employee emp = empFactory.apply("Alice", 101);
Array Constructor References (Type[]::new)
Array constructor references allow creating arrays of specified lengths. The functional interface must accept a single integer argument representing the array length and return the array type, matching IntFunction<Type[]>:
// Array constructor reference: size -> new String[size]
IntFunction<String[]> stringArrayFactory = String[]::new;
String[] words = stringArrayFactory.apply(10); // Array of length 10
// Primary use case: Stream.toArray(IntFunction<A[]> generator)
List<String> fruits = List.of("Apple", "Banana", "Cherry");
String[] fruitArray = fruits.stream().toArray(String[]::new);
Overload Resolution and Compiler Disambiguation
When a class contains overloaded methods, the compiler inspects the target functional interface to identify a unique matching method:
public class OverloadDemo {
public static void print(String s) { System.out.println("String: " + s); }
public static void print(Integer i) { System.out.println("Integer: " + i); }
}
// Disambiguated by target parameter type:
Consumer<String> strConsumer = OverloadDemo::print; // Calls print(String)
Consumer<Integer> intConsumer = OverloadDemo::print; // Calls print(Integer)
Ambiguity Errors
If the compiler cannot uniquely identify a single most specific method, compilation fails with an ambiguity error:
// Integer declares a static toString(int) AND an instance toString().
// For Function<Integer, String>, both fit: toString(int) as a static reference,
// and toString() as an unbound reference whose receiver is the Integer argument.
// COMPILE ERROR: incompatible types: invalid method reference
// reference to toString is ambiguous
// Function<Integer, String> f = Integer::toString;
To resolve the ambiguity, use an explicit lambda such as i -> Integer.toString(i) or i -> i.toString(). A different failure occurs with a raw target such as Function rawFunc = SomeClass::process; when the only overloads accept String and Integer: the raw function type passes an Object, so javac reports that no suitable method is found.
Restrictions on Method References
- No Explicit Arguments: You cannot pass arguments directly inside method reference syntax:
Math::max(10, 20)is invalid syntax. - Target Interface Required: Method references cannot be used with
varor assigned toObjectwithout an explicit cast. - Generics Type Witnesses: If a method is generic, type witnesses appear after the
::operator:Collections::<String>emptyList.
Consider the following code snippet:
String leader = "Java";
Function<String, String> f1 = leader::concat;
BiFunction<String, String, String> f2 = String::concat;
System.out.println(f1.apply(" 17") + " & " + f2.apply("Open", "JDK"));
What is printed when this code is executed?
Compilation fails because leader::concat requires two arguments.
Compilation fails because String::concat cannot be assigned to BiFunction.
Java 17 & Java OpenJDK
Java 17 & OpenJDK
Consider the following code:
java.util.function.IntFunction<String[]> creator = String[]::new;
String[] items = creator.apply(3);
System.out.println(items.length + " " + items[0]);
What is the result of compiling and executing this code?
Throws a ClassCastException at runtime.
Compiles and prints 3 null.
Compilation fails because array constructor references cannot be assigned to IntFunction.
Compiles and prints 0 null.
Given the following class definition:
public class Dispatcher {
public static void route(Integer id) { System.out.print("INT:" + id); }
public static void route(String name) { System.out.print("STR:" + name); }
public static void main(String[] args) {
java.util.function.Consumer<Integer> c = Dispatcher::route;
c.accept(100);
}
}
What is the result of attempting to compile and run this program?
Compilation fails because the method reference Dispatcher::route is ambiguous between the two overloads.
Compilation fails because static methods cannot be assigned to Consumer.
Compiles cleanly and prints INT:100.
Compiles cleanly and prints STR:100 due to type coercion.
Consider the following method declarations inside a class:
public void checkReferences() {
var ref1 = (java.util.function.Supplier<String>) "hello"::toUpperCase; // Line 1
var ref2 = String::trim; // Line 2
Object ref3 = System.out::println; // Line 3
}
Which lines fail compilation?
Line 1 only
Line 2 and Line 3 only
Line 1 and Line 2 only
Line 1, Line 2, and Line 3
Sections you finish are checked off in the contents.