Comparable and Comparator Interfaces in Java - Quiz

Total: 6 questions

1. 

What is the difference between Comparable and Comparator in Java, and when should you use each?

Comparable<T> (package java.lang) defines the natural ordering of a class: the class itself implements int compareTo(T o) and compares this with the argument. A class has only one natural ordering. String, Integer, LocalDate, BigDecimal and every enum already implement it.

Comparator<T> (package java.util) is an external comparison rule: int compare(T o1, T o2) is implemented in a separate class, an anonymous class or a lambda, and one class can have any number of comparators. The class itself does not change, so a comparator works for String and third-party classes. Comparator is a functional interface with ready-made Java 8 methods: comparing(), thenComparing(), reversed().

If the order is obvious and unique (numbers, dates, codes), implement Comparable; for everything else, write a comparator. The two work together: a comparator that is passed in (list.sort(cmp), new TreeSet<>(cmp)) always takes priority over the natural ordering, and without one (or with null) compareTo() is used.

2. 

What do compareTo() and compare() return, and what contract must they follow?

Both methods return an int: a negative number means the first object (this or o1) goes before the second, a positive number means after, and zero means the objects are equal for this ordering (which does not necessarily mean equals() returns true).

Only the sign matters, not the value: String.compareTo() returns the difference of character codes, so the check a.compareTo(b) == 1 is a bug; write a.compareTo(b) > 0.

The contract:

  • the sign of a.compareTo(b) is the opposite of the sign of b.compareTo(a);
  • the order is transitive: if a > b and b > c, then a > c;
  • elements that compare as equal compare the same way with any third element.

If the contract is broken, sorting gives a wrong result or fails with IllegalArgumentException: Comparison method violates its general contract!.

3. 

Why should you not write return a - b in compareTo() or compare(), and what should you use instead?

Subtraction gives the right sign only while the difference fits into an int. On overflow the sign flips: Integer.MIN_VALUE - 1 becomes Integer.MAX_VALUE (2147483647), and the comparator (a, b) -> a - b decides that minus two billion is greater than one. The list [1, -2147483648] stays unsorted after such a sort, and a broken contract can lead to an IllegalArgumentException.

Instead of subtraction, use:

  • Integer.compare(this.age, other.age), Long.compare(), Double.compare();
  • the method reference Integer::compare as a comparator;
  • in chains, Comparator.comparingInt(), comparingLong(), comparingDouble(): they call compare() inside and avoid boxing.

Tests with small numbers do not catch this bug: it shows up with IDs, amounts in cents or negative values.

4. 

What does it mean that compareTo is consistent with equals, and how does it affect TreeSet and TreeMap?

An ordering is consistent with equals() if a.compareTo(b) == 0 exactly when a.equals(b). The Java documentation strongly recommends this but does not require it.

Breaking it does not affect sorting a list, but a TreeSet and the keys of a TreeMap find and drop duplicates by compareTo()/compare(), not by equals(): if the comparison returns 0, the new element counts as already present, and add() returns false without any exception. A HashSet, in contrast, relies on equals() and hashCode().

The classic example from the standard library is BigDecimal: new BigDecimal("1.0") and new BigDecimal("1.00") are not equal by equals() (the scale counts), but compareTo() returns 0. A HashSet keeps two elements, a TreeSet keeps one. In the same way a last-name-only comparator loses the second Smith. If objects live in a TreeSet or serve as TreeMap keys, include in the comparison every field that equals() uses, for example with thenComparing().

5. 

How do you build a comparator with Comparator.comparing, thenComparing and reversed, and how do you sort in descending order?

Since Java 8 a comparator is assembled from the static and default methods of Comparator:

  • comparing(Person::getLastName) — compares by a key (the key must be Comparable); a comparator for the key can be passed as the second argument;
  • comparingInt() / comparingLong() / comparingDouble() — by a primitive key, with no boxing and no overflow;
  • thenComparing(), thenComparingInt() — the next criterion when the previous one returned 0;
  • reversed() — reverses the whole chain it is called on;
  • naturalOrder() / reverseOrder() — natural ordering and its reverse;
  • nullsFirst(cmp) / nullsLast(cmp) — puts null at the start or the end; without them a null in the list causes a NullPointerException.
people.sort(Comparator.comparing(Person::getLastName)
        .thenComparingInt(Person::getAge));                    // last name, then age
people.sort(Comparator.comparingInt(Person::getAge).reversed()); // oldest first
names.sort(Comparator.reverseOrder());                          // strings in descending order

To reverse only one criterion in a chain, pass it as the second argument: thenComparing(Person::getAge, Comparator.reverseOrder()).

6. 

How does String.compareTo() compare strings, and how do you sort words in dictionary order?

String.compareTo() compares strings character by character by their UTF-16 codes and returns the difference between the first pair of characters that differ. So uppercase Latin letters (65–90) come before lowercase ones (97–122), and accented letters such as “É” (U+00C9, code 201) come after every unaccented letter: the natural ordering of peach, Éclair, apple, Banana is [Banana, apple, peach, Éclair].

String.CASE_INSENSITIVE_ORDER removes the difference between cases, but “Éclair” still ends up last because “É” keeps code 201: [apple, Banana, peach, Éclair].

Dictionary order comes from a Collator: it implements Comparator, follows the rules of a language and treats “É” as a variant of “E”.

words.sort(Collator.getInstance(Locale.ENGLISH));
// [apple, Banana, Éclair, peach]
Page 1 of 1