Instance Method Reference on the First Argument
Before You Start
Check each box you can do from memory. A box you cannot check yet is not a problem; it points you to a quick refresher, not a grade.
Not sure? Take the 60-second self-check.
1. Instance vs. static. String.compareTo is an instance method. Integer.compare is a static method. What is the difference between how you call them?
Check
An instance method is called on an object (a receiver): a.compareTo(b). A static method is called on the class: Integer.compare(a, b). The instance method has an implicit “this” receiver; the static method does not.
2. Static method reference. What does Integer::compare mean when used as a Comparator<Integer>?
Check
It is equivalent to (a, b) -> Integer.compare(a, b). For each pair, the static method is called with both arguments explicitly.
3. Shape requirement. Comparator<String> requires a method that takes two String arguments and returns int. How many arguments does String.compareTo(String other) take?
Check
One explicit argument (other). But compareTo is called on a receiver (this), which is also a String. Together: two String inputs (the receiver plus the explicit argument) and one int output. The shape matches Comparator<String>.
Try This First
The lambda (a, b) -> a.compareTo(b) works as a Comparator<String>. Before reading further: guess the method reference form that expresses the same thing.
Check
String::compareTo. The method reference ClassName::instanceMethodName packages the lambda (a, b) -> a.instanceMethod(b) where the first parameter becomes the receiver.
What You Need To Walk In With
The Insight: Class::instanceMethod (unbound instance method reference) turns the first lambda argument into the receiver. String::compareTo means (a, b) -> a.compareTo(b), NOT String.compareTo(a, b). The first String becomes the object the method is called on; the second becomes the argument. This is why a one-argument instance method can satisfy a two-argument functional interface.
Every instance method call a.method(b) has two inputs: the object a (the receiver, implicit as this inside the method) and the explicit argument b. The unbound method reference Class::method makes that implicit receiver explicit as the first lambda parameter. Once you see that, the shape arithmetic becomes mechanical: count the receiver as input one, count each declared parameter as the remaining inputs, and check whether the total matches the functional interface you need.
You can: translate any unbound instance method reference to its lambda equivalent, explain why String::compareTo satisfies Comparator<String> despite its single declared parameter, predict which one-argument instance method references work directly as comparators and which need a Comparator.comparingInt wrapper, and identify all four kinds of method references in code you read.
How It Works
The unbound instance method reference
When the compiler sees String::compareTo in a context that requires Comparator<String>:
compareTois an instance method, not static.- For an instance method reference
ClassName::methodName, the first lambda parameter becomes the receiver. - The mapping is:
compare(a, b)maps toa.compareTo(b). String.compareTo(String other)takes one explicit argument; the receiver is the second String. Total: two String inputs, one int output. MatchesComparator<String>.
This works for any one-argument instance method on the type being compared:
// String::compareTo = (a, b) -> a.compareTo(b)
Comparator<String> alphabetic = String::compareTo;
// String::compareToIgnoreCase = (a, b) -> a.compareToIgnoreCase(b)
Comparator<String> caseInsensitive = String::compareToIgnoreCase;
Key extractors: zero-argument methods need a wrapper
String::length has shape String -> int (one input, not two). Directly using it as a Comparator<String> fails. The Comparator.comparingInt factory wraps a key extractor into a comparator:
// WRONG: String::length is not Comparator<String>
// Comparator<String> byLength = String::length; // compile error
// CORRECT: wrap with comparingInt
Comparator<String> byLength = Comparator.comparingInt(String::length);
Comparator.comparingInt(String::length) applies String::length to each element to extract an int key and then compares those keys with Integer.compare. The full Comparator.comparing by Key API is covered later in this lane.
The four kinds of method references (summary)
| Form | Meaning | Example |
|---|---|---|
Class::staticMethod |
static call: (a, b) -> Class.method(a, b) |
Integer::compare |
Class::instanceMethod |
first-arg-as-receiver: (a, b) -> a.method(b) |
String::compareTo |
instance::method |
bound: receiver fixed: b -> boundInstance.method(b) |
System.out::println |
Class::new |
constructor: args -> new Class(args) |
Player::new |
This page covers the second row: the unbound instance method reference.
Worked Example: Predict Then Check
Determine the lambda equivalent of each method reference, and state whether it works directly as Comparator<String>:
String::compareTo
String::compareToIgnoreCase
String::length
String::isEmpty
Reasoning
String::compareTo: lambda is(a, b) -> a.compareTo(b). One explicit argument + receiver = two Strings. Yes, direct Comparator<String>.String::compareToIgnoreCase: lambda is(a, b) -> a.compareToIgnoreCase(b). Same shape. Yes, direct Comparator<String>.String::length: lambda iss -> s.length(). One String input, int output. Needs two inputs. No. Wrap:Comparator.comparingInt(String::length).String::isEmpty: lambda iss -> s.isEmpty(). One String input, boolean output. Wrong return type. No. Cannot be used as Comparator at all.
Show answers
| Method reference | Direct Comparator? |
|---|---|
String::compareTo |
Yes |
String::compareToIgnoreCase |
Yes |
String::length |
No; wrap with Comparator.comparingInt |
String::isEmpty |
No; wrong return type |
Quick check
Check your understanding
Which of the following method references works directly as Comparator<String> without any wrapper?
String::compareToIgnoreCase is an unbound instance method reference with one explicit String argument plus a String receiver, giving two String inputs and an int output -- the exact shape Comparator<String> requires. The others have either one input (String::length, String::hashCode) or the wrong return type (String::isEmpty).Common Misconceptions
Misconception 1: reading String::compareTo as a static call
Wrong mental model: “
String::compareTocallsString.compareTo(a, b), a static method taking both strings.”
Why it breaks: String has no static compareTo method. compareTo is an instance method. The reference String::compareTo compiles as an unbound instance reference: the first lambda parameter becomes the receiver, the second becomes the explicit argument. The equivalent lambda is (a, b) -> a.compareTo(b), not String.compareTo(a, b).
How to correct: When reading Class::method, first determine whether method is static or instance. For a static method, both arguments are explicit. For an instance method, the first argument becomes the receiver.
Source: JLS §15.13.
Quick check
Check your understanding
A student says: ‘String::compareTo calls the static method String.compareTo(a, b) with both strings as arguments.’ What is wrong with this reading?
Class::method and the method is an instance method, the first lambda parameter fills the receiver slot. The equivalent lambda is (a, b) -> a.compareTo(b), not a call to a nonexistent static String.compareTo. Checking whether a method is static or instance is the first step in reading any method reference.Misconception 2: using a zero-argument method reference directly as a Comparator
Wrong mental model: “
String::lengthgives me a number for each String, so I can use it to compare Strings.”
Why it breaks: String::length has shape String -> int: one input, one output. Comparator<String> needs shape (String, String) -> int: two inputs, one output. The shapes do not match. The compiler reports a type error.
How to correct: Use Comparator.comparingInt(String::length), which wraps the key extractor into a two-argument comparator:
Comparator<String> byLength = Comparator.comparingInt(String::length);
Source: Java 25 Comparator.comparingInt javadoc.
Formal Definition and Interface Contract
From JLS §15.13 (Method Reference Expressions):
For a method reference of the form
ReferenceType :: [TypeArguments] Identifierwhere the method is an instance method, the first formal parameter of the invocation type of the method reference is used as the receiver and the remaining formal parameters [...] are used as the arguments.
From JLS §15.13.3 (Run-Time Evaluation of Method References):
For a method reference to an instance method that is not bound: the target reference for the method invocation is the first argument to the invocation; the rest of the arguments are the formal parameters of the method.
Mental Model
Picture String::compareTo as “the compareTo tool on String objects.” When the tool is handed two strings a and b, it installs a in the receiver slot and b in the argument slot, then runs. The tool’s owner (the class String) is just the namespace; the first input fills the receiver. Compare this to Integer::compare, where no receiver slot exists and both inputs are explicit arguments.
Connections
Within CSCD 210/211: The Comparator.comparing(Person::getLastName) idiom in Comparator.comparing by Key is built on the unbound instance method reference. This page is the foundation for that pattern.
Looking back: Static Method Reference as Comparator covered the static form. This page covers the instance form, which is the trickier one because the first argument silently becomes the receiver.
Looking ahead: When a Method Reference Is Clearer than a Lambda provides the decision rule for choosing between lambdas and method references. Comparator.comparing by Key covers Comparator.comparing and Comparator.comparingInt, which use key-extractor method references internally.
Practice
Level 1
Translate each lambda to an equivalent method reference:
// (a)
Comparator<String> c1 = (a, b) -> a.compareTo(b);
// (b)
Comparator<String> c2 = (a, b) -> a.compareToIgnoreCase(b);
Show answer
// (a)
Comparator<String> c1 = String::compareTo;
// (b)
Comparator<String> c2 = String::compareToIgnoreCase;
Level 2
A student writes Comparator<String> c = String::length;. Explain the compile error and provide two correct alternatives: one using Comparator.comparingInt and one using a lambda.
Show answer
Error: String::length has shape String -> int (one input). Comparator<String> requires two String inputs. The shapes do not match.
Alternative 1 (method reference via comparingInt):
Comparator<String> c = Comparator.comparingInt(String::length);
Alternative 2 (lambda):
Comparator<String> c = (a, b) -> Integer.compare(a.length(), b.length());
Level 3
Explain in 3-4 sentences why String::compareTo works as a Comparator<String> even though compareTo is declared with only one parameter. Use the terms “receiver,” “lambda equivalent,” and “unbound instance method reference.”
Show answer
String::compareTo is an unbound instance method reference: the method is compareTo on the class String, with no specific instance bound at the reference site. Its lambda equivalent is (a, b) -> a.compareTo(b): the first parameter a becomes the receiver (the object on which compareTo is called), and the second parameter b becomes the explicit argument. String.compareTo(String other) takes one explicit parameter, but together with the receiver that is two String inputs, which matches the two-parameter int compare(String, String) shape required by Comparator<String>. The compiler resolves the reference by mapping the first lambda parameter to the receiver slot and the rest to the declared parameter list.
Go Deeper (optional)
None of what follows is needed to write correct code. It is here for the curious.
Where this shows up in production code. Comparator.comparing(Person::getLastName) is one of the most common lines in any Java codebase that sorts custom objects. The reason it reads so cleanly is exactly the unbound instance method reference: Person::getLastName has shape Person -> String, so Comparator.comparing can wrap it into a (Person, Person) -> int comparison automatically. Understanding the shape arithmetic makes the whole sorting API readable rather than magic.
The design pattern underneath. The unbound instance method reference Class::method is the simplest expression of the Strategy pattern: the behavior (how to compare) is separated from the data (what to compare), and passed as a value. This is also the simplest form of a first-class function in Java. The idea that behavior can be a value you pass around traces to the function-object idiom that Java has supported since inner classes, and method references are the modern, syntactically clean version.
The has-a vs. is-a angle. A Comparator<T> has no inheritance relationship with T. It holds a comparison strategy. Bloch, Effective Java Item 43 makes this concrete: all five method reference forms are tabulated with examples, and the bound vs. unbound distinction (fixed receiver at creation time vs. first argument at call time) is the sharpest line to draw.
The order-theory angle. The contract that Comparator enforces (antisymmetry, transitivity, and totality) is the axiom set of a strict total order on the type. Every valid Comparator you write imposes a strict total order; every broken one violates at least one axiom. The method-reference form does not change this contract, but it is worth knowing that the neat one-liner String::compareTo is backed by a mathematically precise agreement about what “before” means.
Check Yourself
Close the notes and answer each one from memory, then reveal it. Pulling an idea back from memory is one of the strongest ways to make it stick.
Check your understanding
What is the lambda equivalent of the method reference String::compareTo when used as a Comparator<String>?
Class::instanceMethod, the first lambda parameter becomes the receiver. So String::compareTo maps to (a, b) -> a.compareTo(b): a is the receiver and b is the explicit argument.Why does String::compareTo satisfy Comparator<String> even though compareTo declares only one explicit parameter?
compareTo(String other) takes one explicit argument, but the instance method also has a receiver (the object it is called on). The unbound method reference maps the first lambda parameter to the receiver slot, giving two String inputs total, which matches (String, String) -> int required by Comparator<String>.A student writes Comparator<String> c = String::length;. Why does this not compile?
String::length is an unbound instance method reference with shape String -> int: one String input, one int output. Comparator<String> requires shape (String, String) -> int: two inputs. The shapes do not match, causing a compile error. The fix is Comparator.comparingInt(String::length).Predict which of the following will compile without error as a Comparator<String> assignment:
(A) Comparator<String> c = String::compareTo;
(B) Comparator<String> c = String::compareToIgnoreCase;
(C) Comparator<String> c = String::length;
(D) Comparator<String> c = String::isEmpty;
String::compareTo and String::compareToIgnoreCase both take one explicit String argument with a String receiver, giving the two-input int-output shape Comparator<String> requires. String::length has shape String -> int (one input) and fails. String::isEmpty has shape String -> boolean (wrong return type) and also fails.Which of the four method reference forms does String::compareTo represent?
Class::instanceMethod with no specific instance bound is an unbound instance method reference. At invocation time, the first lambda argument fills the receiver slot and the rest fill the declared parameters. This is the second row of the four-kind table covered in this concept.