How to sort a list by multiple fields
Short answer
thenComparing() adds the next field — it is compared only when the previous ones are equal:
record Employee(String department, String name) {}
void main() {
var staff = new ArrayList<>(List.of(
new Employee("Sales", "Oleg"),
new Employee("IT", "Boris"),
new Employee("Sales", "Anna"),
new Employee("IT", "Anna")));
staff.sort(Comparator.comparing(Employee::department).thenComparing(Employee::name));
staff.forEach(e -> IO.println(e.department() + " " + e.name()));
}Output:
IT Anna
IT Boris
Sales Anna
Sales OlegDifferent orders
To sort one field in descending order, pass a separate comparator to thenComparing():
record Player(String team, int score) {}
void main() {
var players = new ArrayList<>(List.of(
new Player("Red", 10), new Player("Blue", 30), new Player("Red", 25)));
players.sort(Comparator.comparing(Player::team)
.thenComparing(Player::score, Comparator.reverseOrder()));
IO.println(players);
}Output:
[Player[team=Blue, score=30], Player[team=Red, score=25], Player[team=Red, score=10]]Null values
Comparator.nullsLast() puts elements with null at the end instead of throwing:
record Task(String title, Integer priority) {}
void main() {
var tasks = new ArrayList<>(List.of(new Task("b", null), new Task("a", 2), new Task("c", 1)));
tasks.sort(Comparator.comparing(Task::priority, Comparator.nullsLast(Comparator.naturalOrder())));
IO.println(tasks);
}Output:
[Task[title=c, priority=1], Task[title=a, priority=2], Task[title=b, priority=null]]Common mistake
Putting reversed() at the end of the chain. It reverses the whole comparator, not only the last field:
record Player(String team, int score) {}
void main() {
var players = new ArrayList<>(List.of(
new Player("Red", 10), new Player("Blue", 30), new Player("Red", 25)));
players.sort(Comparator.comparing(Player::team).thenComparing(Player::score).reversed());
IO.println(players);
}Output:
[Player[team=Red, score=25], Player[team=Red, score=10], Player[team=Blue, score=30]]The teams got sorted in descending order too. Specify the order for the particular field, as in the example above.
Functions used in this recipe
- Comparator.thenComparing() — Adds a second sort criterion — for objects equal by the first one.
- Comparator.comparing() — Creates a comparator that compares objects by one key.
- Comparator.reversed() — Returns a comparator with the opposite order. For the natural order there is Comparator.reverseOrder().