← Skill tree CS Skill Tree 0 CSCD211

Instance Method Reference on the First Argument

Textbook: BJP (Reges and Stepp)

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>:

  1. compareTo is an instance method, not static.
  2. For an instance method reference ClassName::methodName, the first lambda parameter becomes the receiver.
  3. The mapping is: compare(a, b) maps to a.compareTo(b).
  4. String.compareTo(String other) takes one explicit argument; the receiver is the second String. Total: two String inputs, one int output. Matches Comparator<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 is s -> s.length(). One String input, int output. Needs two inputs. No. Wrap: Comparator.comparingInt(String::length).
  • String::isEmpty: lambda is s -> 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?

Tier 2 · BJP (Reges and Stepp), Ch 10


Common Misconceptions

Misconception 1: reading String::compareTo as a static call

Wrong mental model:String::compareTo calls String.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?

Tier 2 · BJP (Reges and Stepp), Ch 10


Misconception 2: using a zero-argument method reference directly as a Comparator

Wrong mental model:String::length gives 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] Identifier where 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>?

Tier 1 · BJP (Reges and Stepp), Ch 10

Why does String::compareTo satisfy Comparator<String> even though compareTo declares only one explicit parameter?

Tier 1 · BJP (Reges and Stepp), Ch 10

A student writes Comparator<String> c = String::length;. Why does this not compile?

Tier 2 · BJP (Reges and Stepp), Ch 10

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;

Tier 2 · BJP (Reges and Stepp), Ch 10

Which of the four method reference forms does String::compareTo represent?

Tier 3 · BJP (Reges and Stepp), Ch 10