java-comparable-comparator-ordenacion

Sorting in Java: Comparable and Comparator

  • 3 min

The Comparable and Comparator interfaces define how to establish an order between objects, whether natural or specific to an operation.

If you have a List<String>, sorting it is easy. You call Collections.sort(list) and Java puts it in lexicographic order. If it’s a List<Integer>, it puts it in numerical order.

But what happens if you have a List<User>? Java has no idea how to sort users.

  • By ID?
  • By age?
  • By last name?

If you try Collections.sort(users), the compiler will give you an error. It will say: “I don’t know how to compare these objects.”

To teach Java how to sort, we have two interfaces: Comparable (the default order) and Comparator (alternative orders).

Comparable: the “natural order”

The Comparable<T> interface is implemented inside the class itself that you want to sort. It defines the “default” or “natural” order of the object. For example, the natural order of a number is from smallest to largest, and that of a word is alphabetical.

For our users, we decide that their natural order will be by ID.

Implementation

The class must implement Comparable<User> and override the compareTo method.

public class User implements Comparable<User> {
    private int id;
    private String name;
    private int age;

    // Constructor, getters...

    @Override
    public int compareTo(User otherUser) {
        // Comparison logic:
        // Returns negative if 'this' goes BEFORE 'otherUser'
        // Returns 0 if they are EQUAL
        // Returns positive if 'this' goes AFTER 'otherUser'

        return this.id - otherUser.id; // Mathematical trick for integers
    }
}
Copied!

Now it works:

Collections.sort(users); // It works! Sorts by ID.
Copied!

this.id - other.id works great for simple integers. But be careful with very large numbers (Overflow) or with floats. For those cases, use Integer.compare(a, b) or Double.compare(a, b).

Comparator: the “flexible order”

Okay, now we have users sorted by ID. But what if I now want to get a list sorted by Age? And then another one by Name?

We cannot modify the User class every time. The class can only have one compareTo.

For external criteria we use Comparator, an interface that compares two objects without modifying their class.

Classic style (pre-Java 8)

We create a class that implements Comparator.

public class SortByAge implements Comparator<User> {
    @Override
    public int compare(User u1, User u2) {
        return u1.getAge() - u2.getAge();
    }
}

// Usage:
Collections.sort(users, new SortByAge());
Copied!

Modern style (lambdas - Java 8+)

Creating an entire class for a subtraction is ridiculous. Nowadays we use Lambda expressions.

// Sort by age
users.sort((u1, u2) -> u1.getAge() - u2.getAge());

// Or even better, using method references (The cleanest way):
users.sort(Comparator.comparingInt(User::getAge));
Copied!

Chained Comparators

Imagine you want to sort by Age, but if two have the same age, you want to break ties by Name. With the modern Comparator API, this is a delight:

users.sort(
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName)
);
Copied!