Command Palette

Search for a command to run...

PHASE 10Intermediate Java 8+ ~30 min· topic 3 of 8

Topic 10.3

Method References

In one line

A method reference like String::length or System.out::println is a shorter way to write a lambda that only calls one existing method. There are four kinds: static, bound instance, unbound instance and constructor references.

Think of it like this

Speed dial on a phone. Instead of typing your grandmother's full number every time, you press 2, which refers to the number you already saved. A lambda like s -> s.length() types the whole call out; a method reference String::length just points at a method that already exists and says "use that one".

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

Method reference
An expression with :: that points at an existing method or constructor, used instead of a lambda that only calls it.
Receiver
The object a method is called on. In name.length(), name is the receiver.
Bound reference
A method reference with a specific receiver object fixed at creation, like System.out::println.
Unbound reference
A method reference to an instance method written with the class name, like String::length. The receiver arrives later as the first argument.
Constructor reference
ClassName::new: a reference that creates a new object each time it's called.
Overload
One of several methods with the same name but different parameters (Topic 3.4). The target type decides which overload a method reference picks.

Step by step

01Spot a lambda that only forwards a call

Look at these lambdas: each one takes its parameters and passes them, unchanged and in the same order, to one existing method. That's the pattern a method reference replaces.

If a lambda does anything more (adds a constant, reorders arguments, calls two methods), keep the lambda.

Main.javawhole filejava
s -> Integer.parseInt(s)          // becomes  Integer::parseInt
x -> System.out.println(x)        // becomes  System.out::println
s -> s.toUpperCase()              // becomes  String::toUpperCase
(a, b) -> a.compareTo(b)          // becomes  String::compareTo   (as a Comparator<String>)
() -> new ArrayList<>()           // becomes  ArrayList::new

s -> s.substring(1)               // stays a lambda: the 1 isn't a parameter
x -> Math.max(x, 0)               // stays a lambda

02Static method references

Integer::parseInt, Math::abs, String::valueOf, Objects::isNull, Character::isDigit. The target's parameters become the method's arguments, and its return value (if the target needs one) is the method's result.

Because the target decides, the same reference can resolve to different overloads: Math::abs is abs(int) for an IntUnaryOperator and abs(double) for a DoubleUnaryOperator.

Main.javawhole filejava
Function<String, Integer> parse = Integer::parseInt;     // s -> Integer.parseInt(s)
BinaryOperator<Integer> bigger  = Math::max;            // (a, b) -> Math.max(a, b)
IntUnaryOperator abs1            = Math::abs;            // abs(int)
DoubleUnaryOperator abs2         = Math::abs;            // abs(double)
Predicate<Object> isNull         = Objects::isNull;

03Bound references: the receiver is fixed at creation

In greeting::concat, the expression greeting is evaluated right now and stored inside the method reference object. Reassigning a field later doesn't change which object the reference calls. A lambda s -> greeting.concat(s) reads greeting on every call instead.

For the same reason, nullString::length throws a NullPointerException the moment the reference is created: javac inserts an Objects.requireNonNull check on the receiver.

Bound references: the receiver is fixed at creationdiagram
Rendering diagram…

04Unbound references: the first argument is the receiver

String::length names an instance method but no object. When used as a Function<String, Integer>, the function's one argument is the string to call length() on. With two parameters, like BiFunction<String, String, Boolean> sw = String::startsWith, the first is the receiver and the second is the argument: (s, prefix) -> s.startsWith(prefix).

This is why Comparator.comparing(Person::name) works: comparing wants a Function<Person, String>, and Person::name is "take a person, call name() on it".

If both a static method and an instance method could match, the compiler reports an ambiguity. Integer::toString for Function<Integer, String> could mean the static Integer.toString(int) or the instance i.toString().

terminal
$ javac Main.java
── expected output ──
Main.java:5: error: incompatible types: invalid method reference
Function<Integer, String> f = Integer::toString;
^
reference to toString is ambiguous
both method toString(int) in Integer and method toString() in Integer match
1 error

05Constructor and array references

Point::new picks the constructor whose parameters match the target: a Supplier<Point> uses the no-arg constructor, a Function<Integer, Point> the one-arg constructor, and so on. Each call creates a new object.

For arrays, int[]::new means n -> new int[n]. The most common use is stream.toArray(String[]::new), which tells the stream how to create a correctly typed array of the right size (Topic 10.5).

Main.javawhole filejava
Supplier<List<String>> listMaker = ArrayList::new;         // new ArrayList<>()
Function<String, StringBuilder> sb = StringBuilder::new;     // new StringBuilder(s)
IntFunction<String[]> arrays       = String[]::new;          // n -> new String[n]
String[] names = Stream.of("a", "b").toArray(String[]::new);

06this::method and super::method

Inside an instance method, this::validate is a bound reference to the current object's validate method. It's the usual way to pass a private helper into a stream: orders.stream().filter(this::isEligible).

super::prefix refers to the superclass's version of an overridden method, bound to the current object. It's rare, but it's the only way to pass the parent implementation as a function.

07What the compiler generates

For a lambda, javac creates a private lambda$... method to hold the body (Topic 10.1). For a method reference it usually doesn't need to: the invokedynamic bootstrap arguments point straight at the target method. javap -p shows no synthetic methods at all, and javap -v shows the method handles REF_invokeStatic Integer.parseInt and REF_invokeVirtual String.length.

Some references still get a bridge method because they need extra adaptation: super::prefix compiles to a private lambda$say$0, and int[]::new to a lambda$main$0(int) that runs new int[n].

terminal
$ javap -p Main
javap -v -p Main | grep REF_invoke
── expected output ──
Compiled from "Main.java"
public class Main {
public Main();
public static void main(java.lang.String[]);
}
0: #62 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(...)Ljava/lang/invoke/CallSite;
#49 REF_invokeStatic java/lang/Integer.parseInt:(Ljava/lang/String;)I
1: #62 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(...)Ljava/lang/invoke/CallSite;
#56 REF_invokeVirtual java/lang/String.length:()I

Try it yourself

  1. 1

    Convert lambdas to references

    Write a tiny program with Function<String, String> up = s -> s.toUpperCase();, Consumer<String> out = s -> System.out.println(s); and Supplier<StringBuilder> sb = () -> new StringBuilder();. Rewrite each as a method reference, then run both versions and confirm the output is identical.

  2. 2

    Watch the bound receiver

    In the second example, change current::length to "first"::length and predict the output. Then change viaLambda to () -> "first".length(). Why do both now print 5?

  3. 3

    Trigger the ambiguity

    Add Function<Integer, String> f = Integer::toString; to any example. Read the error, then fix it two ways: String::valueOf and the lambda i -> Integer.toString(i).

Code & diagrams

The four kinds of method reference Java 8+ New tab
Sign in to run this example in your browser.

Expected output

static:      42
bound:       Hello, Asha
unbound:     6
unbound, 2:  true
constructor: [fresh]
array ctor:  length 5
printed through a method reference
x
y
Bound receivers are evaluated once, lambdas every time Java 8+ New tab

viaRef still points at the String "first"; the field now points somewhere else.

Sign in to run this example in your browser.

Expected output

method reference: 5
lambda:           12
lambda over null: created fine
method reference over null: NPE at creation
lambda over null: NPE only when called
The target type chooses the overload Java 8+ New tab

parse returns an Integer, so + 1 is arithmetic. show returns a String, so + 1 is concatenation.

Sign in to run this example in your browser.

Expected output

(0, 0) (3, 3) (1, 2)
8
71
Method references with your own classes: this::, super:: and comparators Java 16+ New tab
Sign in to run this example in your browser.

Expected output

[Person[name=Asha, age=15], Person[name=Meera, age=21], Person[name=Ravi, age=34]]
adults: [Person[name=Meera, age=21], Person[name=Ravi, age=34]]
oldest first: [Person[name=Ravi, age=34], Person[name=Meera, age=21]]
HELLO / Hi, Asha
HELLO / Hi, Bo

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

An ambiguous method reference

Write Function<Integer, String> f = Integer::toString;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:5: error: incompatible types: invalid method reference
Function<Integer, String> f = Integer::toString;
^
reference to toString is ambiguous
both method toString(int) in Integer and method toString() in Integer match
1 error

Break #2

A bound reference on null

Write String name = null; Supplier<Integer> len = name::length; and run it.

terminal
$ java Main.java
── what you'll see ──
Exception in thread "main" java.lang.NullPointerException
at java.base/java.util.Objects.requireNonNull(Objects.java:233)
at Main.main(Main.java:6)

Break #3

Wrong number of parameters for an unbound reference

Write Function<String, Integer> cmp = String::compareTo;.

terminal
$ javac Main.java
── what you'll see ──
Main.java:5: error: incompatible types: invalid method reference
Function<String, Integer> cmp = String::compareTo;
^
unexpected instance method compareTo(String) found in unbound lookup
1 error

Myth vs fact

Myth

Method references are faster than lambdas.

Fact

After JIT compilation they perform the same. The only difference is that javac often skips generating a synthetic lambda$ method for a reference; the runtime linkage through LambdaMetafactory is identical.

Myth

obj::method and () -> obj.method() are always equivalent.

Fact

The reference evaluates obj once at creation (and throws NPE immediately if it's null); the lambda evaluates obj on every call. With a changing field or a null receiver they behave differently.

Myth

String::length calls a static method.

Fact

It's an unbound reference to the instance method length(). The string to measure is supplied as the first argument when the function is called.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    JLS 15.13 resolves ReferenceType::identifier with two searches: one treating it as static (all target parameters as arguments) and one treating it as unbound instance (first parameter as receiver). If both find an applicable method, it's ambiguous; if one finds a static method and the other an instance method in the "wrong" mode, you get messages like unexpected instance method found in unbound lookup.

  • ▸

    The bound receiver null check is javac's own: older javac versions emitted receiver.getClass() for the same effect, newer ones call Objects.requireNonNull. Either way it happens at the invokedynamic capture site, which is why the stack trace points at the creation line.

  • ▸

    Method references that need varargs packing (a Function<String, List<String>> pointing at Arrays::asList), references to super methods, and references to methods inaccessible from the generated class get a synthetic bridge (a lambda$ method) because LambdaMetafactory can only adapt simple signatures directly. Array constructor references (int[]::new) also go through a bridge, since there's no method handle for "new array".

  • ▸

    Comparator.comparing(Person::name) is not slower than a hand-written comparator after inlining, but comparing with a boxed key (comparing(Person::age)) boxes every comparison. Use comparingInt, comparingLong and comparingDouble for primitive keys.

Remember this

  1. 1

    A method reference uses the :: operator: Type::method, object::method, or Type::new. Like a lambda, it has no type on its own; its target type (a functional interface, Topic 10.2) decides which method it means and how arguments are passed. Integer::parseInt as a Function<String, Integer> means s -> Integer.parseInt(s).

  2. 2

    Static reference, ClassName::staticMethod: all the functional method's arguments are passed to the static method. Math::max as a BinaryOperator<Integer> means (a, b) -> Math.max(a, b).

  3. 3

    Bound instance reference, expression::method: the object before :: (the receiver) is evaluated once, when the reference is created, and every call goes to that same object. System.out::println binds to the current System.out. If the expression is null, you get a NullPointerException immediately, not when it's called.

  4. 4

    Unbound instance reference, ClassName::instanceMethod: no receiver yet. The first argument of the functional method becomes the receiver, and the rest become the method's arguments. String::length as a Function<String, Integer> means s -> s.length(); String::compareToIgnoreCase as a Comparator<String> means (a, b) -> a.compareToIgnoreCase(b).

  5. 5

    Constructor reference, ClassName::new: calls a constructor, chosen by the target's parameters. ArrayList::new as a Supplier<List<String>> calls new ArrayList<>(); Person::new as a BiFunction<String, Integer, Person> calls the two-argument constructor. Array constructor references like int[]::new (an IntFunction<int[]>) power stream.toArray(String[]::new).

  6. 6

    Use a method reference when it reads better than the lambda. map(String::trim) is clearer than map(s -> s.trim()); but x -> foo(x, 1) or s -> s.substring(1) can't be method references at all, and Integer::toString is ambiguous for a Function<Integer, String>. You can also write this::method and super::method to refer to the current object's methods.

Explain it without notes

01

What are the four kinds of method reference? Give an example and the equivalent lambda for each.

02

How does the compiler decide which overload a method reference refers to?

03

Explain the difference between obj::method and () -> obj.method().

04

When can't you use a method reference?

Practice

01

Given List<String> words = List.of("pear", "Apple", "fig"), copy it into an ArrayList, sort it case-insensitively using an unbound method reference, and print it. Then print each word with forEach and a bound reference.

02

Use a constructor reference to turn the names "Asha" and "Ravi" into StringBuilders, reverse each, and print them.

03

Write a class Checker with an instance method boolean isShort(String s) (length below 4) and a method List<String> keepShort(List<String> in) that uses this::isShort with removeIf and Predicate.not. Test it on pear, fig, kiwi, ox.

Trade-offs

  • ↔

    Method references are shorter and name the operation (map(String::trim)), but they hide the parameter, so map(this::convert) can be less obvious than map(order -> convert(order)) when the method name is vague. Choose whichever reads better to someone new to the code.

  • ↔

    Bound references evaluate their receiver eagerly, which fails fast on null and avoids re-reading a field, but it surprises people who expect lambda-like late binding when the field changes.

  • ↔

    Overloaded methods make method references fragile: adding a new overload to a class can turn an existing reference into a compile error elsewhere. Lambdas with explicit calls are more robust to API evolution.

Done when you can

  • Done when you can name and write all four kinds of method reference with their equivalent lambdas.

  • Done when you can explain how the first argument becomes the receiver in an unbound reference.

  • Done when you can explain eager receiver evaluation and the null case.

  • Done when you can diagnose an ambiguous method reference and fix it.

  • Done when you can use this::, super::, Type::new and int[]::new where they fit.