Comparable and Comparator contract with regards to null

polygenelubricants picture polygenelubricants · May 18, 2010 · Viewed 17k times · Source

Comparable contract specifies that e.compareTo(null) must throw NullPointerException.

From the API:

Note that null is not an instance of any class, and e.compareTo(null) should throw a NullPointerException even though e.equals(null) returns false.

On the other hand, Comparator API mentions nothing about what needs to happen when comparing null. Consider the following attempt of a generic method that takes a Comparable, and return a Comparator for it that puts null as the minimum element.

static <T extends Comparable<? super T>> Comparator<T> nullComparableComparator() {
   return new Comparator<T>() {
      @Override public int compare(T el1, T el2) {
         return
            el1 == null ? -1 :
            el2 == null ? +1 :
            el1.compareTo(el2);
      }
   };
}

This allows us to do the following:

List<Integer> numbers = new ArrayList<Integer>(
   Arrays.asList(3, 2, 1, null, null, 0)
);
Comparator<Integer> numbersComp = nullComparableComparator();
Collections.sort(numbers, numbersComp);
System.out.println(numbers);
// "[null, null, 0, 1, 2, 3]"

List<String> names = new ArrayList<String>(
   Arrays.asList("Bob", null, "Alice", "Carol")
);
Comparator<String> namesComp = nullComparableComparator();
Collections.sort(names, namesComp);
System.out.println(names);
// "[null, Alice, Bob, Carol]"

So the questions are:

  • Is this an acceptable use of a Comparator, or is it violating an unwritten rule regarding comparing null and throwing NullPointerException?
  • Is it ever a good idea to even have to sort a List containing null elements, or is that a sure sign of a design error?

Answer

cletus picture cletus · May 18, 2010

Comparable doesn't allow null simply because:

a.compareTo(b) == -b.compareTo(a)

for all objects a and b where !a.equals(b). More specifically:

a.equals(b) ? b.equals(a) && a.compareTo(b) == 0 &&
                  b.compareTo(a) == 0 && a.hashCode() == b.hashCode()
            : !b.equals(a) && a.compareTo(b) != 0 &&
                  a.compareTo(b) == -b.compareTo(a)

must evaluate to true to satisfy the relevant contracts.

So null isn't allowed because you can't do:

null.compareTo(a)

Comparator is more flexible so handling of null is an implementation-specific issue. Support it or not depending on what you want your Comparator to do.