Method references in Java
Static, bound instance, unbound instance and constructor references.
s -> 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 behaviourFour 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<>()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);40two43403Compile 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"); // trueBound: 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 hereReassign the receiver
What does this print?
String s = "hey";
Supplier<Integer> len = s::length;
s = "hello!";
System.out.println(len.get());63Compile 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.
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 // ✓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
- Static: Integer::parseInt ≈ s -> Integer.parseInt(s)
- Bound: System.out::println ≈ x -> System.out.println(x)
- Unbound: String::length ≈ s -> s.length()
- Constructor: ArrayList::new ≈ () -> new ArrayList<>()
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);- 123
- 15
- 12abc
- 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 = ___;- String::equals
- String.equals
- String::equals()
- 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.