In standard programming, sorting a list of items by a single field (like name or ID) is basic. However, real-world sorting often requires tie-breakers—for example, sorting employees alphabetically by name, and if two employees share the same name, sorting them next by their ID. Java's Stream API solves this cleanly through Comparator Chaining.
In this guide, we will break down multi-field sorting using a school roll-call analogy and trace the execution path in Java.
Imagine a teacher trying to line up a group of school children. The teacher wants them arranged in a strict line:
- Rule 1 (Primary): First, the teacher tells the children to line up alphabetically by their Name. Everyone begins sorting themselves.
- Rule 2 (Tie-Breaker): Two children, both named "Sourav", find themselves standing next to each other. They don't know who should go first! The teacher gives them a tie-breaker rule: *"If your names are exactly the same, look at your ID badges. The one with the smaller ID number goes first."*
By chaining these rules, the kids arrange themselves perfectly: "Sourav (ID 3)" stands before "Sourav (ID 5)", and both stand before "Test (ID 4)" and "Test (ID 6)".
Walkthrough of the Main Method Scenario
Let's trace how the program executes step-by-step from the entry point of the main method:
The program declares a list of unsorted employees containing identical names but different ID numbers:
List<EmployeeSecond> list = Arrays.asList(
new EmployeeSecond("Sourav", 5),
new EmployeeSecond("Test", 6),
new EmployeeSecond("Sourav", 3),
new EmployeeSecond("Test", 4)
);
This list contains two "Sourav"s and two "Test"s, making it a perfect test case for tie-breakers.
Next, the program initiates a Java Stream, sorts it, and prints the result:
list.stream()
.sorted(Comparator.comparing(EmployeeSecond::getName)
.thenComparing(EmployeeSecond::getId))
.forEach(System.out::println);
Comparator.comparing(EmployeeSecond::getName)acts as the primary sort. It compares name strings alphabetically. For example, "Sourav" is smaller than "Test", so the Souravs are sorted to the front.thenComparing(EmployeeSecond::getId)acts as the secondary tie-breaker. When comparing "Sourav" (ID 5) and "Sourav" (ID 3), the names are equal. The comparator chains down to compare their IDs: 3 is smaller than 5, so "Sourav (ID 3)" is placed first.
The sorted items are passed to the terminal print statement:
{name='Sourav', id=3}
{name='Sourav', id=5}
{name='Test', id=4}
{name='Test', id=6}
The output is sorted perfectly, both by name alphabetically and by ID numerically where names match.
Java Implementation
Below is the complete Java code demonstrating multi-field sorting using Comparator chaining and Streams:
package io.practise.accolite;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class EmployeeSecond {
private String name;
private Integer id;
public EmployeeSecond(String name, Integer id) {
this.name = name;
this.id = id;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "{" + "name='" + name + '\'' + ", id=" + id + '}';
}
public static void main(String[] args) {
List<EmployeeSecond> list = Arrays.asList(
new EmployeeSecond("Sourav", 5),
new EmployeeSecond("Test", 6),
new EmployeeSecond("Sourav", 3),
new EmployeeSecond("Test", 4)
);
list.stream()
.sorted(Comparator.comparing(EmployeeSecond::getName)
.thenComparing(EmployeeSecond::getId))
.forEach(System.out::println);
}
}
Conclusion
In Java, Comparator.comparing() combined with thenComparing() allows you to chain multiple sorting criteria cleanly. This pattern replaces complex nested conditional logic with declarative, readable code that integrates seamlessly with Java Streams.