method references
the four kinds of ::, and what each one desugars to.
- chapter
- chapter 07.03
- reading time
- 5 min read
a method reference like String::length is a lambda whose whole body is one call to a method that already exists, written as a name instead of a body.
there are four kinds: static (Integer::parseInt), bound (System.out::println), unbound (String::length) and constructor (ArrayList::new). the target type decides how the arguments line up.
the picture
you know how half your lambdas end up as x -> something(x)? the lambda adds nothing except a name for the argument, which you immediately hand to someone else. a method reference skips the middleman.
think of a speed dial. instead of writing out the number every time (the lambda body), you point at a contact that's already saved (the method). the phone knows how to dial it.
all four, running
import java.util.*;
import java.util.function.*;
void main() {
// 1. static: Class::staticMethod
Function<String, Integer> parse = Integer::parseInt; // s -> Integer.parseInt(s)
// 2. bound: expression::method, the receiver is fixed right now
String greeting = "hello";
Predicate<String> starts = greeting::startsWith; // p -> greeting.startsWith(p)
// 3. unbound: Class::instanceMethod, the first argument is the receiver
Function<String, Integer> len = String::length; // s -> s.length()
Comparator<String> cmp = String::compareToIgnoreCase;
// (a, b) -> a.compareToIgnoreCase(b)
// 4. constructor: Class::new (arrays too)
Supplier<List<String>> fresh = ArrayList::new; // () -> new ArrayList<>()
IntFunction<int[]> array = int[]::new; // n -> new int[n]
IO.println(parse.apply("42") + 1);
IO.println(starts.test("hell"));
IO.println(len.apply("duke"));
IO.println(cmp.compare("JAVA", "java"));
IO.println(fresh.get());
IO.println(array.apply(3).length);
}43 true 4 0 [] 3
the same :: text can mean different things depending on the target. String::compareToIgnoreCase takes one argument as a method, but as a Comparator<String> it takes two: the first one becomes the object the method is called on. that's the unbound trick, and it's why list.sort(String::compareToIgnoreCase) works.
sort them yourself
the only hard part is telling bound from unbound. the question to ask: is there an actual object to the left of ::, or just a class name?
this, a call) is bound, ::new is always a constructor.when the receiver gets evaluated
here's the subtle difference between a bound reference and the lambda you'd write by hand. the lambda evaluates sb every time it runs. the method reference evaluates sb once, when the reference is created, and keeps that object.
StringBuilder sb = new StringBuilder("a");
Supplier<String> snap = sb::toString;
sb = new StringBuilder("b");
IO.println(snap.get());that also means a null receiver blows up early, at creation, not when you call it:
import java.util.function.*;
void main() {
String name = null;
Supplier<Integer> lazy = () -> name.length(); // fine until get()
IO.println("lambda created");
Supplier<Integer> eager = name::length; // throws right here
IO.println("method ref created");
}lambda created Exception in thread "main" java.lang.NullPointerException
javac literally inserts an Objects.requireNonNull(name) before the invokedynamic, which is why the stack trace points at Objects.requireNonNull and you get no helpful message about which variable was null.
when it's ambiguous
a method reference has to resolve to exactly one method. overloads can make that impossible:
Function<Integer, String> f = Integer::toString;error: incompatible types: invalid method reference
reference to toString is ambiguous
both method toString(int) in Integer and method toString() in Integer matchas a Function<Integer, String>, the Integer argument could be the receiver of toString() (unbound) or the argument to static toString(int). javac refuses to guess. write the lambda (i -> i.toString()), or pick a reference with only one reading, like Object::toString or String::valueOf.
what javac emits for a method reference+
same invokedynamic machinery as a lambda, minus one step. for x -> x.length() javac has to write a private lambda$main$0 method to hold the body. for String::length there's nothing to write: the bootstrap method gets a direct handle to the method you named.
REF_invokeVirtual java/lang/String.length:()I // String::length
REF_invokeStatic java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;for a bound reference, the receiver is evaluated, null-checked with Objects.requireNonNull, and passed to the invokedynamic as a captured argument, exactly like a captured local in a lambda. more in lambda internals.
- a method reference is a lambda that only calls one method. same objects, same
invokedynamic. - static: arguments pass straight through. constructor:
::new, arrays included. - unbound (
Class::method): the first argument becomes the receiver. - bound (
expr::method): the receiver is evaluated once, at creation. null fails right there. - overloads can make a reference ambiguous. then just write the lambda.