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(),nameis 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.
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 lambda02Static 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.
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.
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().
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).
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].
Try it yourself
- 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);andSupplier<StringBuilder> sb = () -> new StringBuilder();. Rewrite each as a method reference, then run both versions and confirm the output is identical. - 2
Watch the bound receiver
In the second example, change
current::lengthto"first"::lengthand predict the output. Then changeviaLambdato() -> "first".length(). Why do both now print 5? - 3
Trigger the ambiguity
Add
Function<Integer, String> f = Integer::toString;to any example. Read the error, then fix it two ways:String::valueOfand the lambdai -> Integer.toString(i).
Code & diagrams
Expected output
static: 42
bound: Hello, Asha
unbound: 6
unbound, 2: true
constructor: [fresh]
array ctor: length 5
printed through a method reference
x
yviaRef still points at the String "first"; the field now points somewhere else.
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 calledparse returns an Integer, so + 1 is arithmetic. show returns a String, so + 1 is concatenation.
Expected output
(0, 0) (3, 3) (1, 2)
8
71Expected 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, BoBreak 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;.
Break #2
A bound reference on null
Write String name = null; Supplier<Integer> len = name::length; and run it.
Break #3
Wrong number of parameters for an unbound reference
Write Function<String, Integer> cmp = String::compareTo;.
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::identifierwith 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 likeunexpected 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 callObjects.requireNonNull. Either way it happens at theinvokedynamiccapture site, which is why the stack trace points at the creation line. - ▸
Method references that need varargs packing (a
Function<String, List<String>>pointing atArrays::asList), references tosupermethods, and references to methods inaccessible from the generated class get a synthetic bridge (alambda$method) becauseLambdaMetafactorycan 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, butcomparingwith a boxed key (comparing(Person::age)) boxes every comparison. UsecomparingInt,comparingLongandcomparingDoublefor primitive keys.
Remember this
- 1
A method reference uses the
::operator:Type::method,object::method, orType::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::parseIntas aFunction<String, Integer>meanss -> Integer.parseInt(s). - 2
Static reference,
ClassName::staticMethod: all the functional method's arguments are passed to the static method.Math::maxas aBinaryOperator<Integer>means(a, b) -> Math.max(a, b). - 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::printlnbinds to the currentSystem.out. If the expression isnull, you get aNullPointerExceptionimmediately, not when it's called. - 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::lengthas aFunction<String, Integer>meanss -> s.length();String::compareToIgnoreCaseas aComparator<String>means(a, b) -> a.compareToIgnoreCase(b). - 5
Constructor reference,
ClassName::new: calls a constructor, chosen by the target's parameters.ArrayList::newas aSupplier<List<String>>callsnew ArrayList<>();Person::newas aBiFunction<String, Integer, Person>calls the two-argument constructor. Array constructor references likeint[]::new(anIntFunction<int[]>) powerstream.toArray(String[]::new). - 6
Use a method reference when it reads better than the lambda.
map(String::trim)is clearer thanmap(s -> s.trim()); butx -> foo(x, 1)ors -> s.substring(1)can't be method references at all, andInteger::toStringis ambiguous for aFunction<Integer, String>. You can also writethis::methodandsuper::methodto refer to the current object's methods.
Explain it without notes
What are the four kinds of method reference? Give an example and the equivalent lambda for each.
How does the compiler decide which overload a method reference refers to?
Explain the difference between obj::method and () -> obj.method().
When can't you use a method reference?
Practice
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.
Use a constructor reference to turn the names "Asha" and "Ravi" into StringBuilders, reverse each, and print them.
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, somap(this::convert)can be less obvious thanmap(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::newandint[]::newwhere they fit.