Instantiating a Comparator and Passing It to Arrays.sort
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. Constructing an instance. What does new TeamComparator() produce?
Check
A fresh instance of TeamComparator. The new keyword allocates the object; the parentheses invoke the no-argument constructor. The result is a value of type TeamComparator, which also satisfies Comparator<Player> because the class implements that interface.
2. Arrays.sort signature. Arrays.sort has an overload that takes two arguments. What are they?
Check
The first argument is the array to sort. The second is a Comparator<T> instance that defines the ordering. The method reorders the array in place and returns nothing.
3. In-place vs. copy. After Arrays.sort(arr, cmp), is arr modified or does the call return a new sorted array?
Check
arr is modified in place. Arrays.sort returns void. There is no new array.
Try This First
Player[] nflPlayers contains five players. TeamComparator is in package cscd211Comparators. Write the single line of Java that sorts nflPlayers in place by team. Do not use a temporary variable.
Check
Arrays.sort(nflPlayers, new TeamComparator());
What You Need To Walk In With
A comparator class is just a declaration until something creates an instance of it. The instance is the value that Arrays.sort needs. Constructing the instance inline with new TeamComparator() is idiomatic when the comparator is stateless (it holds no data) and used exactly once at this call site. The instance is created, used during the sort, and immediately eligible for garbage collection.
A useful mental shorthand: read new Foo() as “create one Foo object right now and hand it to whatever needs it.” The comparator class is the recipe; new produces one cake from that recipe. You can write that sort call, explain why new is required, trace what happens to the array and the comparator object after the sort returns, predict the compile error when the class name is passed without new, and choose whether to inline the allocation or hoist it to a named variable based on how many call sites share it.
How It Works
The canonical call
From F19.Lab1.CSCD211Lab1.java, cases 4-6:
// case 4: sort by team
Arrays.sort(nflPlayers, new TeamComparator());
// case 5: sort by position
Arrays.sort(nflPlayers, new PositionComparator());
// case 6: sort by games played
Arrays.sort(nflPlayers, new GamesPlayedComparator());
Each line does three things:
new TeamComparator()constructs a comparator instance. This calls the no-argument constructor; the class has no fields, so the instance has no state.- The instance is passed to
Arrays.sortas the second argument.Arrays.sortreceives aComparator<Player>value. Arrays.sortcallscmp.compare(a, b)on pairs of elements and reordersnflPlayersin place. The comparator is invoked many times; the array is modified;Arrays.sortreturnsvoid.
What Arrays.sort requires
From the Java 25 Arrays.sort API:
static <T> void sort(T[] a, Comparator<? super T> c)
The second parameter type is Comparator<? super T>. For Player[], T is Player, so the second argument must be a Comparator<Player> (or Comparator<Object>, which also qualifies). A class name without new is not a value; an int from a compare call is not a Comparator. Only an instance of a class that implements Comparator<Player> satisfies this requirement.
Quick check
Check your understanding
What does Arrays.sort(nflPlayers, new TeamComparator()) pass as its second argument?
When to store the comparator in a variable
The inline form is correct when the comparator is used once. If the same comparator is needed at multiple call sites, storing it saves repeated allocation:
// For one-off use: inline is idiomatic
Arrays.sort(nflPlayers, new TeamComparator());
// For repeated use: hoist to a named variable or static field
Comparator<Player> byTeam = new TeamComparator();
Arrays.sort(nflPlayers, byTeam);
Arrays.sort(nflPlayers2, byTeam);
Static Comparator Fields as Named Orders covers the static final field idiom (Player.BY_TEAM) for comparators shared across many call sites.
Worked Example: Predict Then Check
Player[] players = buildRoster(); // 5 players in arbitrary order
System.out.println("Before: " + Arrays.toString(players));
TeamComparator cmp = new TeamComparator();
Arrays.sort(players, cmp);
System.out.println("After: " + Arrays.toString(players));
System.out.println("cmp is still: " + cmp);
Predict: (a) Is players modified? (b) Does cmp still reference a valid object after the sort?
Step-by-step reasoning
Arrays.sort reorders elements in the array it receives. It does not copy the array. So players is modified in place.
cmp is a local variable that holds a reference to the TeamComparator instance. Arrays.sort uses that instance’s compare method but does not nullify or replace the reference. After the sort, cmp still points to the same object.
Show answer
(a) players is modified in place. The before and after printouts show the same five players in different order.
(b) cmp still references the same TeamComparator instance. It carries no state from the sort and could be reused in another Arrays.sort call.
Common Misconceptions
Misconception 1: passing the class name without new
Wrong mental model: “
TeamComparatoris a comparator, soArrays.sort(arr, TeamComparator)should work.”
Why it breaks: TeamComparator (without parentheses) is a type name, not an instance. The compiler reports a type-mismatch error because Arrays.sort expects a Comparator<Player> value, and a type name does not satisfy that.
How to correct: Arrays.sort(arr, new TeamComparator()). The new keyword constructs the instance that provides the value.
Misconception 2: passing the result of compare instead of the comparator
Wrong mental model: “I want to compare by team, so I’ll call
compare.”
// WRONG
Arrays.sort(nflPlayers, new TeamComparator().compare(playerA, playerB));
Why it breaks: new TeamComparator().compare(playerA, playerB) returns an int. Arrays.sort expects a Comparator<Player>, not an int. The compiler rejects the call with a type mismatch.
How to correct: Pass the comparator object itself. Arrays.sort calls compare internally on whatever pairs it needs; the caller does not pre-invoke compare.
Arrays.sort(nflPlayers, new TeamComparator());
Misconception 3: re-instantiating a stateless comparator inside a loop
Wrong mental model: “I want a fresh comparator each iteration.”
for (Player[] division : divisions) {
Arrays.sort(division, new TeamComparator()); // unnecessary new each pass
}
Why it matters: TeamComparator has no fields. Every instance is identical. Creating a new one each iteration allocates an object that has the same behavior as the previous one, adding garbage-collection pressure with no benefit.
How to correct: For comparators used many times, hoist the instance out of the loop:
Comparator<Player> byTeam = new TeamComparator();
for (Player[] division : divisions) {
Arrays.sort(division, byTeam);
}
For a single one-off sort, inline new TeamComparator() is fine.
Source: Bloch, Effective Java, Item 6.
Quick check
Check your understanding
A student writes: Arrays.sort(nflPlayers, new TeamComparator().compare(nflPlayers[0], nflPlayers[1])). What type does the second argument have, and what type does Arrays.sort require?
Formal Definition and Interface Contract
From the Java 25 Arrays API:
public static <T> void sort(T[] a, Comparator<? super T> c)
Sorts the specified array of objects according to the order induced by the specified comparator. All elements in the array must be mutually comparable using the specified comparator (that is,
c.compare(e1, e2)must not throw aClassCastExceptionfor any elementse1ande2in the array).This sort is guaranteed to be stable: equal elements will not be reordered as a result of the sort.
The sort is stable: two elements that compare equal remain in their original relative order. This matters when sorting by team name and two players share a team.
Mental Model
Think of the comparator instance as an instruction card handed to a sorting worker. The card says “sort by team name.” The worker (Arrays.sort) reads the card when deciding which element comes first between any two. The worker does not need you to pre-read the card; handing it over is the only job. Writing new TeamComparator() is the act of printing the card; passing it to Arrays.sort is handing it to the worker.
Connections
Within CSCD 210/211: Implementing Comparator<T> as a Named Class, The cscd211Comparators Package Convention, and this lesson together form the complete workflow for a named comparator class. This is the “use” step. Arrays.sort with a Comparator covers the difference between Arrays.sort (in-place, for arrays) and list.sort (in-place, for lists) and Collections.sort (also for lists).
Looking back: The cscd211Comparators Package Convention established the package structure and file header. This lesson assumes the comparator file is correctly declared and compiles.
Looking ahead: Anonymous-Class Syntax for Comparator replaces the named class with an anonymous inner class written at the call site. Writing a Comparator as a Lambda replaces the anonymous class with a lambda.
Go Deeper (optional)
None of what follows is needed to write correct sort calls. It is here for when you are curious about the “why” underneath the “what.”
Where this pattern lives in professional Java. Almost every production sort call in Java either passes new SomeComparator() inline or uses a comparator built with Comparator.comparing. Both forms reduce to exactly this shape: the sort receives a comparator value, not a class reference. Knowing this pattern cold means you can read and modify any sorting code in a real codebase without hesitation.
The named class as the simplest function object. The whole point of wrapping compare in a class is to make a comparison into a first-class value that can be stored, passed, and invoked later. That idea goes by two names in software-design literature. As a design pattern, it is the Strategy pattern: you separate the algorithm (how to compare) from the context that uses it (Arrays.sort). As a type-theory idea, it is the earliest Java spelling of a first-class function. Writing a Comparator as a Lambda shows how a lambda compresses the named-class form to one line, but both are the same underlying idea.
Why the signature says Comparator<? super T> and not Comparator<T>. The wildcard ? super T is a lower-bounded wildcard (JLS section 4.5.1). For a Player[], it means you may pass a Comparator<Player>, a Comparator<Object>, or any comparator for a supertype of Player. This is safe because any such comparator can compare two Player values. It makes the API more reusable: a single Comparator<Object> that orders by toString() can be passed to sort arrays of any type.
What Arrays.sort actually does. For object arrays (which comparator sorts always are), the JDK uses TimSort, a hybrid merge-sort algorithm that runs in O(n log n) worst case and exploits natural runs of already-sorted data. For primitive arrays, the JDK switches to dual-pivot quicksort. The comparator form always uses TimSort and is therefore always stable.
Practice
Level 1
Player[] nflPlayers contains 32 players. GamesPlayedComparator (in cscd211Comparators) orders players by games played ascending. Write the single line of Java that sorts nflPlayers in place by games played.
Show answer
Arrays.sort(nflPlayers, new GamesPlayedComparator());
Pattern from F19.Lab1.CSCD211Lab1.java case 6.
Level 2
A student writes:
Arrays.sort(nflPlayers, new TeamComparator().compare(nflPlayers[0], nflPlayers[1]));
Identify the type of the second argument, state what type Arrays.sort requires, and write the corrected line.
Show answer
The second argument has type int (the return type of compare). Arrays.sort requires Comparator<Player>.
Corrected line:
Arrays.sort(nflPlayers, new TeamComparator());
Level 3
You have four divisions, each stored as a Player[]. All four must be sorted by games played. Write the code, making exactly one allocation of GamesPlayedComparator. Explain why one allocation is sufficient.
Thought process
Hoist the comparator instance outside the loop. Because GamesPlayedComparator has no fields, a single instance behaves identically on every call to compare.
Show answer
Player[][] divisions = { north, south, east, west };
Comparator<Player> byGames = new GamesPlayedComparator();
for (Player[] division : divisions) {
Arrays.sort(division, byGames);
}
One allocation is sufficient because GamesPlayedComparator holds no state. Every compare call on the single instance produces the same result as a call on any other instance of the same class. Allocating four separate instances would add garbage with no behavioral difference.
Source: Bloch, Effective Java, Item 6 (avoid creating unnecessary objects).
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
Which line correctly sorts a Player[] array named nflPlayers by team using TeamComparator?
After Arrays.sort(players, new TeamComparator()) returns, which statement is true?
A student writes: Arrays.sort(nflPlayers, new TeamComparator().compare(nflPlayers[0], nflPlayers[1])). What does the compiler report?
A program sorts four Player[] divisions in a loop, writing new GamesPlayedComparator() inside the loop each iteration. What is the best objection to this approach?
Given: Comparator<Player> byTeam = new TeamComparator(); Arrays.sort(roster, byTeam); Arrays.sort(bench, byTeam); How many TeamComparator instances are constructed in total?