Sorting and comparators
Lists often need ordering: names alphabetically, products by price, employees by department and salary. Java can sort by itself — you only have to explain the order.
Natural order
Numbers and strings already know how to compare with each other. This is called natural order. The Collections.sort() method sorts a list by it:
void main() {
List<Integer> numbers = new ArrayList<>(List.of(5, 2, 9, 1));
Collections.sort(numbers);
IO.println(numbers);
}Output:
[1, 2, 5, 9]Strings are sorted by character codes: capital letters come before lowercase ones.
A comparator and reverse order
A comparator (Comparator) is an object that compares two elements and thereby defines an order. The list's sort() method accepts a comparator. The ready-made Comparator.reverseOrder() sorts in descending order:
void main() {
List<String> names = new ArrayList<>(List.of("Oleg", "Anna", "Ira"));
names.sort(Comparator.reverseOrder());
IO.println(names);
}Output:
[Oleg, Ira, Anna]Comparator.comparing()
For your own objects a comparator is conveniently built with Comparator.comparing(): you tell it which value to compare by. The notation Product::price is a method reference, a short way to say "take price() from each product":
record Product(String title, double price) {}
void main() {
List<Product> products = new ArrayList<>(List.of(
new Product("Tea", 4.5),
new Product("Coffee", 7.0),
new Product("Juice", 3.2)));
products.sort(Comparator.comparing(Product::price));
for (Product p : products) {
IO.println(p.title() + " " + p.price());
}
}Output:
Juice 3.2
Tea 4.5
Coffee 7.0Add .reversed() and the order flips: Comparator.comparing(Product::price).reversed().
Several fields
If the values of the first field are equal, the second field is given with the thenComparing() method. Here employees are sorted by department, and within a department by name:
record Employee(String name, String department) {}
void main() {
List<Employee> staff = new ArrayList<>(List.of(
new Employee("Oleg", "warehouse"),
new Employee("Anna", "sales"),
new Employee("Ira", "warehouse"),
new Employee("Boris", "sales")));
staff.sort(Comparator.comparing(Employee::department).thenComparing(Employee::name));
staff.forEach(e -> IO.println(e.department() + ": " + e.name()));
}Output:
sales: Anna
sales: Boris
warehouse: Ira
warehouse: OlegYour own natural order: Comparable
A class can define its own natural order by implementing the Comparable interface and its compareTo() method. The method returns a negative number if the current object is smaller than the other, zero if they are equal, and a positive number if it is greater:
record Version(int major, int minor) implements Comparable<Version> {
@Override
public int compareTo(Version other) {
if (major != other.major) {
return Integer.compare(major, other.major);
}
return Integer.compare(minor, other.minor);
}
}
void main() {
List<Version> versions = new ArrayList<>(List.of(
new Version(2, 1), new Version(1, 9), new Version(2, 0)));
Collections.sort(versions);
IO.println(versions);
}Output:
[Version[major=1, minor=9], Version[major=2, minor=0], Version[major=2, minor=1]]Common mistake
Sorting changes the list itself. An unmodifiable list from List.of() cannot be sorted:
void main() {
List<Integer> numbers = List.of(3, 1, 2);
Collections.sort(numbers);
}Output:
Exception in thread "main" java.lang.UnsupportedOperationExceptionMake a modifiable copy first — List<Integer> sorted = new ArrayList<>(numbers); — and sort that.