Java course menu
Java Sorting: Comparable, Comparator, thenComparing and reversed
Learn to sort in Java: natural ordering with Comparable and compareTo, custom orders with Comparator, Comparator.comparing, thenComparing, reversed and nullsFirst, sorting lists and arrays of objects, stable sorting, and sorted collections like TreeSet and TreeMap.
On this page
Sorting numbers and strings is easy, because Java already knows their order. To sort your own objects, such as students by marks or products by price, you tell Java how to compare them. There are two ways: Comparable, the object's one natural order, and Comparator, any number of custom orders.
Sorting built-in types#
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(42, 7, 19, 3));
Collections.sort(nums);
System.out.println(nums);
int[] array = {5, 2, 9, 1};
Arrays.sort(array);
System.out.println(Arrays.toString(array));
List<String> words = new ArrayList<>(List.of("banana", "Cherry", "apple"));
words.sort(null); // natural order
System.out.println(words);
words.sort(String.CASE_INSENSITIVE_ORDER); // ignore case
System.out.println(words);
}
}[3, 7, 19, 42]
[1, 2, 5, 9]
[Cherry, apple, banana]
[apple, banana, Cherry]The natural order of strings compares character codes, and every uppercase letter comes before every lowercase one, so "Cherry" sorts first. String.CASE_INSENSITIVE_ORDER ignores case.
Comparable: the natural order#
A class defines its natural order by implementing Comparable<T> and its single method compareTo, which returns a negative number if this comes first, zero if equal, and positive if this comes after:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class Version implements Comparable<Version> {
final int major;
final int minor;
Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
@Override
public int compareTo(Version other) {
if (major != other.major) {
return Integer.compare(major, other.major);
}
return Integer.compare(minor, other.minor);
}
@Override
public String toString() {
return major + "." + minor;
}
}
public class Main {
public static void main(String[] args) {
List<Version> versions = new ArrayList<>(List.of(
new Version(2, 1), new Version(1, 9), new Version(2, 0), new Version(1, 10)));
Collections.sort(versions);
System.out.println(versions);
}
}[1.9, 1.10, 2.0, 2.1]Use Integer.compare(a, b) rather than a - b, which can overflow for large values. String, wrapper classes, LocalDate and enums all implement Comparable.
Comparator: any order you like#
A Comparator<T> is a separate object that compares two values. With the factory methods in Comparator, most orders take one line:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
record Student(String name, int marks, int age) {}
public class Main {
public static void main(String[] args) {
List<Student> students = new ArrayList<>(List.of(
new Student("Ravi", 85, 21),
new Student("Asha", 92, 20),
new Student("Meera", 85, 19),
new Student("John", 78, 22)));
students.sort(Comparator.comparing(Student::name));
System.out.println(names(students));
students.sort(Comparator.comparingInt(Student::marks).reversed());
System.out.println(names(students));
students.sort(Comparator.comparingInt(Student::marks).reversed()
.thenComparing(Student::name));
System.out.println(names(students));
students.sort(Comparator.comparingInt(Student::age));
System.out.println(names(students));
}
static List<String> names(List<Student> list) {
List<String> out = new ArrayList<>();
for (Student s : list) out.add(s.name());
return out;
}
}[Asha, John, Meera, Ravi]
[Asha, Meera, Ravi, John]
[Asha, Meera, Ravi, John]
[Meera, Asha, Ravi, John]Student::name is a method reference, a short form of the lambda s -> s.name(); see Lambdas.
Look at the second line: Meera and Ravi both have 85, and Meera stays before Ravi because the list was already sorted by name. Java's object sort is stable: elements that compare as equal keep their previous relative order. The third sort gives the same result, but by stating the tie-breaker with thenComparing it no longer depends on the list's previous order, which is safer.
Comparable or Comparator?#
- Implement
Comparablewhen a class has one obvious natural order (versions, dates, money). - Use a
Comparatorfor any other order, for classes you cannot change, or when you need several orders.
Sorted collections#
TreeSet, TreeMap and PriorityQueue keep their contents ordered by the natural order, or by a Comparator passed to the constructor:
import java.util.Comparator;
import java.util.TreeSet;
public class Main {
public static void main(String[] args) {
TreeSet<String> byLength = new TreeSet<>(
Comparator.comparingInt(String::length).thenComparing(Comparator.naturalOrder()));
byLength.addAll(java.util.List.of("banana", "fig", "apple", "kiwi", "date"));
System.out.println(byLength);
}
}[fig, date, kiwi, apple, banana]A sorted set treats elements that compare as 0 as duplicates. Without the thenComparing tie-breaker, "kiwi" and "date" (both length 4) would count as the same element, and one would be dropped.
Common mistakes#
Practice#
Practice exercise
Given products as records Product(String name, String category, double price): Laptop/Electronics/55000, Pen/Stationery/25, Monitor/Electronics/12000, Notebook/Stationery/60, Headphones/Electronics/2500. Sort them by category, and within a category by price from highest to lowest, and print the names.
Show solution
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
record Product(String name, String category, double price) {}
public class SortProducts {
public static void main(String[] args) {
List<Product> products = new ArrayList<>(List.of(
new Product("Laptop", "Electronics", 55000),
new Product("Pen", "Stationery", 25),
new Product("Monitor", "Electronics", 12000),
new Product("Notebook", "Stationery", 60),
new Product("Headphones", "Electronics", 2500)));
products.sort(Comparator.comparing(Product::category)
.thenComparing(Comparator.comparingDouble(Product::price).reversed()));
for (Product p : products) {
System.out.println(p.category() + " " + p.name() + " " + p.price());
}
}
}Electronics Laptop 55000.0
Electronics Monitor 12000.0
Electronics Headphones 2500.0
Stationery Notebook 60.0
Stationery Pen 25.0.reversed() is applied to the price comparator only, inside thenComparing, so the category order stays ascending.
Key takeaway#
Finished this lesson?