Posts Tagged “java”

Implement Java’s compareTo() the right way

Java’s Comparable#compareTo() looks simple, but small mistakes can create issues that are difficult to find — especially with sorted collections like TreeSet and TreeMap, which rely on ordering to determine uniqueness.

Why do you need compareTo()?

Java’s Comparable interface defines a method for comparing two objects. This can be used to sort your objects in their natural order. Typical examples for natural ordering are

  • String → alphabetical order
  • LocalDate → chronological order

In business code, natural ordering should represent the most common and expected meaning of “sorted”. For example, sorting line items by position in an order is usually what humans expect.

How to implement compareTo()

A correct compareTo() must follow these rules:

  • Anti-symmetric: signum(a.compareTo(b)) == -signum(b.compareTo(a))
  • Transitive: If a > b and b > c, then a > c
  • Consistent: If a.compareTo(b) == 0, they are considered equal in ordering

Also: it is recommended (but not required) that compareTo() is consistent with equals(). We will see why that is important later.

So let’s take the same domain example as before: an order with multiple items. Imagine a type representing a line item in an order:

  • id → database identity (unique)
  • position → business ordering inside the order (1, 2, 3, …)

We want:

  • Natural ordering: by position (ascending)
  • Alternative ordering: by id (ascending)

A naive implementation could look like this:

public record LineItem(long id, int position) implements Comparable<LineItem> {
    @Override
    public int compareTo(LineItem o) {
        return Integer.compare(this.position, o.position); // risky: compareTo==0 for different ids
    }
}
Fig. 1: Naive implementation of `compareTo()` for `LineItem`

Looks reasonable — but this innocent one-liner can silently lose data from your collections without a single exception or warning. Can you spot the flaw?

Read More