Bringing Order to Chaos with Sorting

In the previous blog of this series, we learned how to store and organise data using flexible structures like Lists and Maps. We can now easily create a dynamic list of students or look them up instantly by their ID.
But storing data is rarely enough. To make information useful, we often need to present it in a specific order. Whether it's ranking students by their grades, listing products by price, or organising contacts alphabetically, Sorting is a fundamental operation in software development.
This article explores how Java handles sorting for custom objects using the Comparable and Comparator interfaces.
The Challenge of Sorting Objects
Sorting numbers (3 < 9) or strings (lexicographical order) is straightforward because they have a natural ordering defined by the language.
However, sorting custom objects like Student is more complex. A Student object is a collection of attributes: name, surname, ID, fee, year of birth. Java cannot guess which attribute you want to use for sorting.
Do you want to order them by ID?
By Fee (ascending or descending)?
Alphabetically by Surname?
Because of this ambiguity, we cannot simply use relational operators like < or > between two Student objects. Instead, we must explicitly define our comparison logic.
Crucially, once this logic is defined, we do not need to implement a sorting algorithm (like Bubble Sort or Selection Sort) ourselves. Java provides a powerful, optimised sorting mechanism via Collections.sort(). All we need to do is provide the "rules of comparison," and Java handles the rest.
Custom Strategies: The Comparator Interface
The most flexible way to define sorting rules is by implementing the Comparator interface. This acts as a separate "strategy" or contract where we define the specific logic for comparison in a method called compare(Object o1, Object o2).
The contract expects this method to return an integer that indicates the relative order of the two objects:
Negative Integer (e.g., -1): The first object (
o1) is "less than" the second (o2) and should come before it.Zero (0): The two objects are considered equal for the purpose of this sort.
Positive Integer (e.g., 1): The first object (
o1) is "greater than" the second (o2) and should come after it.
By implementing this contract, we can create different strategies for sorting the same list in different ways without changing the Student class itself.
Example: Sorting by Student ID
public class StudentNumberComparator implements Comparator<Student> {
@Override
public int compare(Student o1, Student o2) {
// Contract: Compare the student numbers
if (o1.getStudentNumber() < o2.getStudentNumber()) return -1;
else if (o1.getStudentNumber() > o2.getStudentNumber()) return 1;
else return 0;
}
}
We can then pass this specific strategy to the sort method:
// Sort using our specific rule
Collections.sort(students, new StudentNumberComparator());
Natural Ordering: The Comparable Interface
Sometimes, an object has a "default" or "natural" order that makes sense in most contexts (e.g., Strings are naturally alphabetical). In these cases, we can build the sorting logic directly into the object's class itself using the Comparable interface.
Unlike Comparator, which is a separate class, Comparable is implemented by the class you want to sort. You define the compareTo(Object other) method inside the class definition (Student in our case).
Example: Sorting Students by Surname (Natural Order)
public class Student implements Comparable<Student> {
// ...
@Override
public int compareTo(Student other) {
// We are assuming that Student natural ordering is by Surname
// and we are reusing String's built-in compareTo
return this.surname.compareTo(other.getSurname());
}
}
Now, we can call Collections.sort() without providing any extra arguments, and it will automatically use this natural ordering:
// Uses the compareTo method inside the Student class
Collections.sort(students);
Conclusion
We have now mastered the art of organising data.
Comparators give us the flexibility to define multiple sorting strategies (by ID, by Fee, by Year) that we can plug into our sort function as needed.
Comparable allows us to define a default "natural" order for our objects, simplifying code when a standard sort is all that is required.
With our knowledge of Collections and Sorting now complete, we can efficiently store, search, and organise data in memory. However, there is one major problem remaining: amnesia. As soon as our program stops, all this data in RAM vanishes. In the next article, we will solve this by learning how to Persist Data—saving our organised collections to permanent files on the disk.



