Arrays.sort with a Comparator
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. Return type. What does Arrays.sort(arr, cmp) return?
Check
void. The method mutates the array in place and returns nothing.
2. Primitive vs. reference. Can you pass a Comparator<int> to Arrays.sort on an int[]?
Check
No. Java has no generic type Comparator<int>. The Arrays.sort(int[], Comparator) overload does not exist. To sort a primitive array with a custom comparator, convert to Integer[] first.
3. Stability. After Arrays.sort(players, byTeam) with a comparator that returns 0 for two players on the same team, what is the relative order of those two players?
Check
Their original relative order is preserved. Arrays.sort for object arrays uses TimSort, which is a stable sort: equal elements maintain their original order.
Try This First
Predict what the variable sorted holds after this code runs:
Player[] players = loadRoster();
Player[] sorted = Arrays.sort(players, new TeamComparator());
Check
The code does not compile. Arrays.sort returns void; the result cannot be assigned. The correct form is:
Player[] players = loadRoster();
Arrays.sort(players, new TeamComparator());
// players is now sorted in place; use players directly
What You Need To Walk In With
The Insight: Arrays.sort is not a function that produces a sorted copy. It is a command that reorders the existing array in place. After the call, the original array reference still points to the same array object, but the elements are in a different order. There is no return value to capture, and there is no new array.
This pattern holds consistently across Java: every mutating sort in the standard library (Arrays.sort, Collections.sort, list.sort) returns void. The convention is that a method either modifies its argument OR returns a new object, not both. Once you recognize this, writing the correct call and avoiding the assignment mistake becomes automatic. You can: write a single-line Arrays.sort call with any Comparator, predict that the array is modified in place and no value is returned, produce a sorted copy with clone() when the original must be preserved, and identify which exception each failure condition throws.
How It Works
The method signature
From the Java 25 Arrays API:
public static <T> void sort(T[] a, Comparator<? super T> c)
Key points:
voidreturn type: the method has no return value. The array argument is modified.T[](object array): only reference-type arrays have this overload.int[],long[],double[]etc. have separate overloads with noComparatorparameter.Comparator<? super T>: the comparator may be forTor any supertype ofT. AComparator<Object>can sort aPlayer[].- TimSort (Java 7+): the algorithm is a stable hybrid merge sort. Stability means equal elements preserve their original relative order.
The F19 Lab 1 calls
// F19.Lab1.CSCD211Lab1.java cases 4, 5, 6
Arrays.sort(nflPlayers, new TeamComparator());
Arrays.sort(nflPlayers, new PositionComparator());
Arrays.sort(nflPlayers, new GamesPlayedComparator());
Each call reorders nflPlayers in place. The array holds the same 32 Player objects throughout; only their positions in the array change.
Producing a sorted copy
If the original order must be preserved, clone before sorting:
Player[] byTeam = nflPlayers.clone();
Arrays.sort(byTeam, new TeamComparator());
// nflPlayers is unchanged; byTeam is sorted
Primitive arrays and the absence of a Comparator overload
Arrays.sort(int[]) sorts ascending using the built-in numeric ordering. There is no Arrays.sort(int[], Comparator<int>) because Comparator<int> is not a valid Java type (Java generics do not work with primitive types). To sort a primitive array in a custom order, the only standard-library options are:
- Box to
Integer[]and useArrays.sort(Integer[], Comparator<Integer>). - Sort ascending and then reverse the array manually.
Worked Example: Predict Then Check
Player[] players = { pC, pA, pB }; // players in arbitrary order
System.out.println("Before: " + Arrays.toString(players));
Arrays.sort(players, new TeamComparator());
System.out.println("After: " + Arrays.toString(players));
// pC.team = "Bears", pA.team = "Aces", pB.team = "Bears"
Predict: (a) what players contains before the sort, (b) what it contains after, (c) what value Arrays.sort returns.
Reasoning
Before: [pC, pA, pB] in the order given at construction.
TeamComparator orders by team name: "Aces" < "Bears". pA (Aces) comes first. pC and pB both have "Bears" and compare equal; their original relative order (C before B) is preserved by TimSort’s stability.
Arrays.sort returns void.
Show answer
Before: [pC, pA, pB]
After: [pA, pC, pB] (Aces first, then Bears in original relative order)
Return value: none (void).
Quick check
Check your understanding
Given this code, what does the variable result hold after the call completes?
Player[] roster = loadRoster();
Player[] result = Arrays.sort(roster, new TeamComparator());
Common Misconceptions
Misconception 1: expecting Arrays.sort to return the sorted array
Wrong mental model: “
Player[] sorted = Arrays.sort(players, cmp);stores the sorted result.”
Why it breaks: Arrays.sort returns void. The assignment is a compile error: “incompatible types: void cannot be converted to Player[]”.
How to correct: Sort in place and use the original reference:
Arrays.sort(players, cmp);
// players is now sorted; use players
If a sorted copy is needed:
Player[] sorted = players.clone();
Arrays.sort(sorted, cmp);
Quick check
Check your understanding
A student needs the original roster preserved while also having a sorted copy. Which of the following achieves that correctly?
Misconception 2: sorting a primitive array with a Comparator
Wrong mental model: “
Arrays.sort(intArr, (a, b) -> b - a)sortsint[]descending.”
Why it breaks: Arrays.sort has no overload that accepts a Comparator for int[]. The compiler reports “no suitable method found for sort(int[], (a, b) -> ...)”. Java generics cannot be parameterized with primitive types.
How to correct for descending int[]:
// Option 1: box to Integer[] first
Integer[] boxed = Arrays.stream(intArr).boxed().toArray(Integer[]::new);
Arrays.sort(boxed, Comparator.reverseOrder());
// Option 2: sort ascending then reverse in-place (only for fully reversing)
Arrays.sort(intArr);
for (int i = 0, j = intArr.length - 1; i < j; i++, j--) {
int tmp = intArr[i]; intArr[i] = intArr[j]; intArr[j] = tmp;
}
Misconception 3: assuming Arrays.sort handles null elements gracefully
Wrong mental model: “If the array has some null elements,
Arrays.sortwill put them at the end or skip them.”
Why it breaks: Arrays.sort calls the comparator on every pair. If the comparator dereferences a null argument (which the standard coding pattern requires a check for), the call throws NullPointerException. Arrays.sort does not filter nulls.
How to correct: Either remove nulls before sorting, or use a null-tolerant wrapper:
Arrays.sort(players, Comparator.nullsLast(new TeamComparator()));
Comparator.nullsLast(cmp) places nulls after all non-null elements and delegates non-null pairs to cmp. reversed, nullsFirst, and nullsLast covers this wrapper in detail.
Formal Definition and Interface Contract
From the Java 25 Arrays API:
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.
Throws:
IllegalArgumentExceptionif the comparator is found to violate theComparatorcontract.
Runtime exception summary:
| Condition | Exception |
|---|---|
a is null |
NullPointerException |
c is null and array has >1 element |
NullPointerException |
An array element is null and comparator dereferences it |
NullPointerException |
| Comparator violates transitivity | IllegalArgumentException |
Mental Model
Think of Arrays.sort as an in-place rearrangement command, not a query. It is like telling a group of people standing in a line “arrange yourselves by height.” After the command, the line is reordered; no new line is created. The line (the array) is the same physical structure, but the positions are different. If you want to keep the original line intact, you need to copy the line first and then rearrange the copy.
Connections
Within CSCD 210/211: The F19 Lab 1 main class calls Arrays.sort three times, illustrated in Instantiating a Comparator and Passing It to Arrays.sort. This lesson formally defines what that call does, what it returns, and what can go wrong.
Looking back: Instantiating a Comparator and Passing It to Arrays.sort covered how to pass a named comparator to Arrays.sort. This lesson covers the contract of that call.
Looking ahead: list.sort (and Collections.sort) with a Comparator covers list.sort(cmp) and Collections.sort(list, cmp) for List<T>, which have the same stability guarantee but operate on a list rather than an array.
Go Deeper (optional)
Nothing here is required to write correct, working Java. Read on only if a connection pulls you in.
The void-return convention you see in Arrays.sort is not an accident. It is the same discipline David Parnas described in his 1972 paper on information hiding: a well-designed module does exactly one thing, and a sorting routine’s one job is to rearrange its argument. Returning a value would imply the method is also producing something new, muddying the contract. When you want a new sorted sequence instead of an in-place rearrangement, the idiom switches: Arrays.stream(arr).sorted().toArray() returns a fresh array and leaves the original untouched, which is the functional style.
A Comparator is also a specific mathematical structure. It imposes a total order on a set: a binary relation that is irreflexive (nothing comes before itself), asymmetric (if A before B then not B before A), and transitive (if A before B and B before C then A before C). These are exactly the axioms of a strict total order, and they are the same rules the Comparator contract enforces. When Arrays.sort throws IllegalArgumentException for a broken comparator, it has detected a violation of one of those axioms, usually transitivity.
The Comparator itself is an early example of the Strategy design pattern: instead of baking one ordering into the sort algorithm, the algorithm accepts an interchangeable strategy object that decides what “less than” means. This is the simplest form of what functional programmers call a first-class function, and it is why Java could later replace new TeamComparator() with the lambda (a, b) -> a.team.compareTo(b.team) without changing Arrays.sort at all.
In production code, in-place sorting is the default for efficiency: no allocation, no garbage collection pressure. The clone-before-sort pattern you practiced in Level 3 is the standard technique when a caller needs both orderings simultaneously, and you will see it routinely in data-processing pipelines, reporting layers, and anywhere a dataset is displayed in multiple views at once. TimSort, the algorithm behind Arrays.sort since Java 7, is also Python’s built-in list sort, chosen independently by both teams for the same reason: O(n log n) worst-case with O(n) behavior on nearly-sorted data, which real-world data often is.
Practice
Level 1
Sort Player[] nflPlayers by team name using TeamComparator. Then print the result. Write both lines.
Show answer
Arrays.sort(nflPlayers, new TeamComparator());
System.out.println(Arrays.toString(nflPlayers));
Arrays.sort returns void; the result is used through the original nflPlayers reference.
Pattern from F19.Lab1.CSCD211Lab1.java case 4.
Level 2
A student writes:
int[] scores = {42, 7, 100, 3};
Arrays.sort(scores, (a, b) -> b - a);
Explain the compile error and provide two correct alternatives for descending sort of an int[].
Show answer
Compile error: Arrays.sort(int[], Comparator) does not exist. Java has no Comparator<int>.
Alternative 1: Sort ascending then reverse:
Arrays.sort(scores);
// reverse scores manually
for (int i = 0, j = scores.length - 1; i < j; i++, j--) {
int tmp = scores[i]; scores[i] = scores[j]; scores[j] = tmp;
}
Alternative 2: Box to Integer[]:
Integer[] boxed = { 42, 7, 100, 3 };
Arrays.sort(boxed, Comparator.reverseOrder());
Level 3
You need to display players sorted by team, while preserving the original roster order for a different output. Write the code that sorts a copy of Player[] roster by team without modifying roster.
Show answer
Player[] byTeam = roster.clone();
Arrays.sort(byTeam, new TeamComparator());
// roster is unchanged; byTeam is sorted by team
System.out.println("Original roster: " + Arrays.toString(roster));
System.out.println("By team: " + Arrays.toString(byTeam));
clone() produces a shallow copy: a new array of the same length containing the same Player references. Sorting byTeam reorders the references in that copy without touching roster.
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 does Arrays.sort(players, new TeamComparator()) return?
Which of the following calls will NOT compile because Arrays.sort has no matching overload?
Trace this code and predict what is printed. pC.team = “Bears”, pA.team = “Aces”, pB.team = “Bears”. Player[] players = { pC, pA, pB }; Arrays.sort(players, new TeamComparator()); System.out.println(Arrays.toString(players));
A student needs the original roster order preserved after sorting. Which code pattern is correct?
According to the Java 25 Arrays API, which condition causes Arrays.sort to throw IllegalArgumentException rather than NullPointerException?