λ Lambdas & Functional Java · Intermediate

Method references in Java

Static, bound instance, unbound instance and constructor references.

🧩 The mysterys -> s.length() and String::length do the same thing... usually. One of them even compiles where the other refuses. Let's meet the double colon.

Lambdas that just call a method

When a lambda only forwards to an existing method, write a method reference instead: Class::method or object::method. No parentheses, no arguments: the target type supplies them.

Function<String, Integer> a = s -> s.length();
Function<String, Integer> b = String::length;
// same behaviour

Four kinds

Static: a class's static method. Bound: a method on one specific object. Unbound: an instance method where the first argument becomes the receiver. Constructor: ::new.

Integer::parseInt   // s -> Integer.parseInt(s)
System.out::println // x -> out.println(x)
String::length      // s -> s.length()
ArrayList::new      // () -> new ArrayList<>()
🔮 Predict it

Your turn

What does this print?

Function<String, Integer> parse =
    Integer::parseInt;
Function<String, Integer> len = String::length;
int a = parse.apply("40");
int b = len.apply("two");
System.out.println(a + b);
  1. 40two
  2. 43
  3. 403
  4. Compile error
Show the answer

parse.apply("40") returns 40, len.apply("two") returns 3. Both are stored in ints, so a + b is arithmetic: 43.

Unbound: who's the receiver?

With an unbound reference, the first parameter becomes the object the method is called on. That's how a one-argument equals fits a two-input BiFunction.

BiFunction<String, String, Boolean> eq =
    String::equals;     // (a, b) -> a.equals(b)
eq.apply("hi", "hi");   // true

Bound: receiver evaluated NOW

With a bound reference, the receiver expression is evaluated once, immediately, and stored. In list.forEach(System.out::println), System.out is looked up once; println runs later, per element.

String s = "hi";
Supplier<Integer> len = s::length;
// "hi" is captured right here
🔮 Predict it

Reassign the receiver

What does this print?

String s = "hey";
Supplier<Integer> len = s::length;
s = "hello!";
System.out.println(len.get());
  1. 6
  2. 3
  3. Compile error
Show the answer

The reference grabbed the String "hey" when it was created. Reassigning s later doesn't affect it: 3. A lambda () -> s.length() would not compile here, because s isn't effectively final.

⚠️ The trap

Syntax slips

Method references use **::, never a dot, and never parentheses**. Adding () would mean "call it now", which isn't a reference at all.

String.equals      // ✗ not a reference
String::equals()    // ✗ no parentheses
String::equals      // ✓
💼 In the real world

Readable pipelines

names.stream().map(String::trim).filter(Predicate.not(String::isEmpty)).map(User::new) reads almost like English. Most teams prefer a method reference whenever a lambda would just forward its arguments, and IDEs suggest the swap automatically.

Key takeaways

  1. Static: Integer::parseInt ≈ s -> Integer.parseInt(s)
  2. Bound: System.out::println ≈ x -> System.out.println(x)
  3. Unbound: String::length ≈ s -> s.length()
  4. Constructor: ArrayList::new ≈ () -> new ArrayList<>()
🤯 Did you know?

Lambdas and method references compile to the same machinery: an invokedynamic call that asks LambdaMetafactory to build the function object at runtime, the first time that line runs.

Practice questions

What does this print?

Function<String, Integer> parse = Integer::parseInt;
Function<String, Integer> len = String::length;
int a = parse.apply("12");
int b = len.apply("abc");
System.out.println(a + b);
  1. 123
  2. 15
  3. 12abc
  4. Compile error
Check your answer

15. parse.apply("12") returns 12 and len.apply("abc") returns 3. Both are ints, so a + b is 15.

Which method reference fits?

BiFunction<String, String, Boolean> eq = ___;
  1. String::equals
  2. String.equals
  3. String::equals()
  4. this::equals
Check your answer

String::equals. String::equals is an unbound reference: the first argument becomes the receiver, so (a, b) -> a.equals(b). Method references never use parentheses or a dot.

Next: lambdas can read local variables, but try to change one and the compiler stops you cold. Why?