← Skill tree CS Skill Tree 0 CSCD211

Anonymous-Class Syntax for Comparator

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. Named-class sort call. Write the single-line sort call that sorts Player[] players by team using a named TeamComparator class.

Check
Arrays.sort(players, new TeamComparator());

2. Anonymous class concept. An anonymous class is a class with no name, defined and instantiated in a single expression. Where does the class body appear?

Check

Inside the new expression, enclosed in braces: new InterfaceName() { ... body ... }. The braces contain the class body; there is no separate file.

3. this inside an anonymous class. Inside new Comparator<Player>() { ... }, what does this refer to?

Check

this refers to the anonymous class instance itself, not to the object that contains the anonymous-class expression. To reach the outer object, write OuterClass.this.


Try This First

The following call uses a named TeamComparator class:

Arrays.sort(players, new TeamComparator());

Imagine removing the separate file TeamComparator.java and writing the class body directly inside the Arrays.sort call. Before reading further, sketch what that might look like.

Compare your sketch
Arrays.sort(players, new Comparator<Player>() {
    @Override
    public int compare(final Player a, final Player b) {
        if (a == null || b == null)
            throw new IllegalArgumentException("Bad Player in compare");
        return a.getTeam().compareTo(b.getTeam());
    }
});

This is the anonymous-class form. The class name (TeamComparator) is gone. The interface name (Comparator<Player>) and the class body appear right at the call site.


What You Need To Walk In With

The Insight: A named comparator class is a type plus one instance. The anonymous-class form collapses both into a single expression at the call site: the class definition IS the object, and there is no separate file. Recognizing this form means you can read older Java code without confusion, even before you prefer the lambda shorthand.

After working through this, you will be able to read an anonymous-class comparator and name the interface it implements and the type parameter it carries, explain why this inside the anonymous body does not refer to the enclosing object, rewrite a simple anonymous-class comparator as a lambda, and state why the type parameter must appear explicitly in new Comparator<Player>() but not in an equivalent lambda. You can: read legacy Java sort calls without confusion, translate them to the modern lambda form, and fix the this-reference compile error that trips up most newcomers.


How It Works

The syntax

Arrays.sort(players, new Comparator<Player>() {
    @Override
    public int compare(final Player a, final Player b) {
        if (a == null || b == null)
            throw new IllegalArgumentException("Bad Player in compare");
        return a.getTeam().compareTo(b.getTeam());
    }
});

Decomposed:

Part Meaning
new Comparator<Player>() { ... } Declares a nameless class that implements Comparator<Player> and creates one instance of it
<Player> The type parameter; must be written here because the compiler does not infer it from context for anonymous classes
@Override Verifies the method name against the interface
final Player a, final Player b Parameters; final is the coding standard
The brace-delimited body after the ) closing the new call The entire class body

The new Comparator<Player>() { ... } expression evaluates to a Comparator<Player> value. Passing it to Arrays.sort is identical to passing new TeamComparator(). The difference is that the class lives only here, in this expression, with no name.

Quick check

Check your understanding

What does the expression new Comparator<Player>() { ... } evaluate to when passed as the second argument to Arrays.sort?

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

Historical context

Before Java 8 (2014), the anonymous-class form was the only way to express a comparator inline without a separate file. It appears in:

In modern Java, a lambda is shorter, clearer, and preferred for this use case (Bloch, Effective Java, Item 42). The anonymous-class form is worth recognizing and reading, but not writing for new code when the body fits in a lambda.

Why this is different inside an anonymous class

Inside the compare method of an anonymous class, this refers to the anonymous class instance. There is no name for this instance; the only way to reach the outer class’s this is OuterClass.this. Lambdas do not have this complication: this inside a lambda always refers to the enclosing object.


Worked Example: Predict Then Check

What does the following expression evaluate to?

new Comparator<Book>() {
    @Override
    public int compare(final Book a, final Book b) {
        return a.getIsbn().compareTo(b.getIsbn());
    }
}
Reasoning

The new X() { ... } expression creates a new object and returns a reference to it. The type is the interface being implemented. The result is a value of type Comparator<Book>, satisfying the second argument of Arrays.sort.

Show answer

(B) A Comparator<Book> instance. The expression creates one nameless instance of the anonymous class and evaluates to a reference to that instance. (JLS §15.9.5.)

Now translate the anonymous class above to a lambda:

Show lambda equivalent
(a, b) -> a.getIsbn().compareTo(b.getIsbn())

Or, using a method reference:

Comparator.comparing(Book::getIsbn)

The lambda requires no new, no Comparator<Book>, no @Override, and no method signature. The type is inferred from the context in which the lambda is used.


Common Misconceptions

Misconception 1: forgetting the type parameter, using raw Comparator

Wrong mental model:new Comparator() { ... } is shorter. The compiler will figure out the type.”

Why it breaks: With raw Comparator, the override becomes compare(Object, Object). Every access to a Player field requires a cast. The compiler issues unchecked warnings. If the array contains a non-Player element, the cast fails at runtime rather than at compile time.

How to correct: Always write the type parameter: new Comparator<Player>() { ... }. Or use a lambda, which avoids the issue entirely.

Source: Bloch, Effective Java, Item 26.


Misconception 2: assuming this inside the anonymous class refers to the enclosing object

Wrong mental model: “The anonymous class is written inside my outer class, so this.field gives me my outer class’s field.”

Why it breaks: Inside new Comparator<Player>() { ... }, this is the anonymous class instance. It has no fields except what the anonymous class body declares. Writing this.playerList when playerList is a field on the enclosing class produces a compile error (“cannot find symbol”).

How to correct: Use OuterClass.this.playerList to reach the enclosing object’s field. Alternatively, assign the field to a local final variable before the anonymous class expression and capture the variable. In modern code, use a lambda instead: this in a lambda is always the enclosing object.

Source: JLS §15.9.5; Bloch, Effective Java, Item 42.


Quick check

Check your understanding

A class named Roster has a field int cutoff. Inside new Comparator<Integer>() { ... } written within Roster, a student writes this.cutoff. What happens and why?

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

Formal Definition and Interface Contract

From JLS §15.9.5 (Anonymous Class Declarations):

An anonymous class declaration is automatically derived from a class instance creation expression by the Java compiler. An anonymous class is never abstract, is always an inner class, and is always implicitly final.

From the Java 25 Comparator API: the single required method is int compare(T o1, T o2), which the anonymous class body must provide with @Override.

From JLS §15.27.3 (lambda target typing): a lambda expression is target-typed from the surrounding context; an anonymous class is not. That is why new Comparator<Player>() must include <Player> explicitly while a lambda in the same position infers it.


Mental Model

Think of the anonymous-class form as “compressed” named-class code. The named-class form separates the class into a separate file with a name. The anonymous-class form folds the file contents into a single expression at the call site and erases the name. The object that comes out is identical in behavior; only the syntax is different. A lambda is a further compression: the compare method signature is also elided, because the compiler infers it from context.


Connections

Within CSCD 210/211: Writing a Comparator as a Lambda replaces the anonymous class with a lambda. That transition, from named class to anonymous class to lambda, is the progression this lane teaches. The anonymous class is the middle step.

Looking back: Instantiating a Comparator and Passing It to Arrays.sort established the named-class form. The anonymous class is the same structure, compressed into a single expression.

Looking ahead: Writing a Comparator as a Lambda shows how lambdas replace anonymous classes for simple comparators. Static Method Reference as Comparator introduces method references, which further compress the lambda.


Practice

Level 1

Read the following code and answer: (a) What interface does the anonymous class implement? (b) What is the class’s name? (c) What does the expression evaluate to?

Arrays.sort(books, new Comparator<Book>() {
    @Override
    public int compare(final Book a, final Book b) {
        return a.getIsbn().compareTo(b.getIsbn());
    }
});
Show answer

(a) Comparator<Book>. (b) It has no name; it is an anonymous class. (c) The new Comparator<Book>() { ... } expression evaluates to a Comparator<Book> instance, which is passed directly to Arrays.sort.


Level 2

Rewrite the following anonymous-class comparator as a lambda expression. Then state what is gained and what (if anything) is lost.

Comparator<Player> byGames = new Comparator<Player>() {
    @Override
    public int compare(final Player a, final Player b) {
        return Integer.compare(a.getGamesPlayed(), b.getGamesPlayed());
    }
};
Show answer

Lambda form:

Comparator<Player> byGames = (a, b) -> Integer.compare(a.getGamesPlayed(), b.getGamesPlayed());

Gained: shorter, no boilerplate (new Comparator<Player>(), @Override, full method signature).

Lost: the lambda cannot be used when the body references this and means the enclosing object (in that situation, use the anonymous-class form or a named class). For a simple delegation body like this one, there is no loss.


Level 3

A student writes the following code inside a class that has a field int threshold:

Arrays.sort(scores, new Comparator<Integer>() {
    @Override
    public int compare(final Integer a, final Integer b) {
        if (a >= this.threshold) return -1;   // move high scores first
        return Integer.compare(a, b);
    }
});

The code does not compile. Explain why, and provide two fixes.

Show answer

Problem: Inside the anonymous class, this refers to the anonymous class instance, which has no field threshold. The field belongs to the enclosing class.

Fix 1: Use OuterClass.this.threshold to reach the enclosing object:

if (a >= OuterClass.this.threshold) return -1;

Fix 2: Capture threshold in a local final variable before the anonymous class:

final int t = this.threshold;
Arrays.sort(scores, new Comparator<Integer>() {
    @Override
    public int compare(final Integer a, final Integer b) {
        if (a >= t) return -1;
        return Integer.compare(a, b);
    }
});

Fix 3 (modern): Use a lambda. this in a lambda is the enclosing object:

Arrays.sort(scores, (a, b) -> {
    if (a >= this.threshold) return -1;
    return Integer.compare(a, b);
});

Source: JLS §15.9.5; Bloch, Effective Java, Item 42.


Go Deeper (optional)

None of this section is needed to write correct code today. It is here for readers who find the “why” as satisfying as the “how.”

Where this shows up in professional work. Any Java codebase written before 2014 (and many since, if the team has not done a modernization pass) uses anonymous-class comparators in sorting, event handling, and callback wiring. Reading this form without hesitation is a practical skill for any Java developer inheriting an existing project, and interview problems often present pre-lambda Java to test whether a candidate can trace execution through the less-familiar syntax.

The design-pattern connection. A Comparator passed as an argument is a concrete instance of the Strategy pattern (Gamma et al., 1994): the sorting algorithm is separated from the comparison rule, and the rule is swapped in at the call site. The anonymous class is simply a way to create that strategy object without giving the strategy a name. In languages that have first-class functions, a plain function serves the same role. Java’s journey from named class to anonymous class to lambda is the language progressively closing that gap.

The scope-binding distinction. The reason this behaves differently in an anonymous class versus a lambda is not arbitrary syntax. An anonymous class is a new class declaration, so it introduces a new binding for this (the anonymous instance). A lambda is a closure over the enclosing scope: it does not introduce a new this, so this resolves the same way it would in the surrounding method. This distinction maps to the theoretical difference between a new binding scope (class body) and a captured environment (closure). Bloch, Effective Java, Item 42 calls this out explicitly as one of the remaining reasons to keep the anonymous-class form rather than converting to a lambda.

The type-inference asymmetry. The reason you must write <Player> in new Comparator<Player>() but not in a lambda is that anonymous classes predate the target-typing rules added in Java 8. A lambda is typed from its target (the expected type at the call site), so the compiler works backward from Comparator<Player> to fill in the type. An anonymous class is not target-typed in the same way; the <Player> in the new expression is the declaration site of the type parameter, not an inferred value. This asymmetry is one of the reasons lambdas read more cleanly for simple cases.


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

In the expression new Comparator<Player>() { ... }, what does the expression evaluate to?

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

Why must you write the type parameter explicitly in new Comparator<Player>() but not in a lambda used in the same position?

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

Inside an anonymous-class comparator, this refers to which object?

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

Predict whether the following code compiles. The anonymous class appears inside a class named Roster that has a field int cutoff. ``java Arrays.sort(scores, new Comparator<Integer>() { @Override public int compare(final Integer a, final Integer b) { if (a >= this.cutoff) return -1; return Integer.compare(a, b); } }); ``

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

Which of the following best describes the relationship between the named-class form and the anonymous-class form of a Comparator?

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