All topics
library
intermediate

Method References & Four Types

Use method references as concise alternatives to lambdas, master all four types, and know when lambdas are clearer.

Method references are shorthand for lambdas that do nothing but call an existing method. They read better, and the interview value is in knowing the four forms and the one that behaves differently from how it looks.

Method reference = giving someone your business card (the method) instead of writing down your phone number and explaining how to call (the lambda). Both reach you, but the card is more direct.

Key Concepts

1
Static reference: Integer::parseInt. Equivalent to s -> Integer.parseInt(s). The arguments of the functional interface become the arguments of the method.
Integer::parseInts -> Integer.parseInt(s)
2
Reference to an instance of a particular object: System.out::println. The receiver is fixed at the moment the reference is created — System.out is captured — and the lambda's arguments are passed to the method. Because the receiver is captured, a reference created from a field captures that field's current value, which is why capturing a mutable field can surprise you later.
System.out::printlnSystem.out
3
Reference to an instance method of an arbitrary object: String::toLowerCase. This is the form people misread. There is no receiver yet; the first argument becomes the receiver. So String::toLowerCase is s -> s.toLowerCase(), and String::compareTo is (a, b) -> a.compareTo(b) — an arity that looks wrong until you see the shift.
String::toLowerCases -> s.toLowerCase()String::compareTo(a, b) -> a.compareTo(b)
4
Constructor reference: ArrayList::new. Equivalent to () -> new ArrayList<>(), and it adapts to the constructor's arity, so String::new can be a Function<char[], String>. With arrays, int[]::new is a IntFunction<int[]>, which is what toArray wants.
ArrayList::new() -> new ArrayList<>()String::newFunction<char[], String>int[]::new
5
They are not free of evaluation order. expensive().method() written as a bound reference evaluates expensive() once, when the reference is created — not on each call. That is usually what you want and occasionally a bug, and it is the difference between obj::method and () -> obj.method() when obj is recomputed.
expensive().method()expensive()obj::method() -> obj.method()obj
6
Readability is the actual criterion. map(this::convert) is clearer than map(x -> convert(x)), and map(User::getName) is clearer than the lambda. But a method reference that forces the reader to work out which of the four forms is in play is worse than the explicit lambda, and choosing on those grounds is the mature answer.
map(this::convert)map(x -> convert(x))map(User::getName)
7
What the interviewer is probing.1. "What are the four kinds?" Probing: recall with understanding. Stalls: naming two. Moves up: static, bound instance, unbound instance, constructor — with an example of each.
8
2. "What does String::toLowerCase mean?" Probing: the unbound form. Stalls: "It calls toLowerCase." Moves up: the first argument becomes the receiver: s -> s.toLowerCase().
String::toLowerCases -> s.toLowerCase()
9
3. "Bound or unbound — how do you tell?" Probing: the distinction. Stalls: guessing. Moves up: instance::method captures a receiver; Type::method takes it from the first argument.
instance::methodType::method
10
4. "When is the receiver evaluated?" Probing: the subtle one. Stalls: "On each call." Moves up: at reference creation for a bound reference — so a changing field is captured once.
11
5. "When would you prefer a lambda?" Probing: judgement. Stalls: "Never, references are cleaner." Moves up: when the reference is ambiguous to read, or when you need to adapt arguments the reference cannot.
12
  The four forms
13
  1. Static
     Integer::parseInt         ->  s -> Integer.parseInt(s)
     Math::max                 ->  (a,b) -> Math.max(a,b)
14
  2. Bound instance (receiver captured now)
     System.out::println       ->  x -> System.out.println(x)
     this::convert             ->  x -> this.convert(x)
     logger::debug             ->  m -> logger.debug(m)
15
  3. Unbound instance (FIRST ARG becomes the receiver)
     String::toLowerCase       ->  s -> s.toLowerCase()
     String::compareTo         ->  (a,b) -> a.compareTo(b)
     User::getName             ->  u -> u.getName()
16
  4. Constructor
     ArrayList::new            ->  () -> new ArrayList<>()
     String::new               ->  cs -> new String(cs)
     int[]::new                ->  n -> new int[n]
17
  Arity shift that catches people
18
     Comparator<String> c = String::compareTo;
     // compareTo takes ONE argument, Comparator needs TWO
     // -> works, because the first argument is the receiver:
     //    (a, b) -> a.compareTo(b)
19
  Capture timing
20
     Bound:    Foo f = a;  Runnable r = f::run;   // captures a NOW
               f = b;                             // r still uses a
     Lambda:   Runnable r = () -> f.run();        // f must be
                                                  // effectively final