Договор сравнения и компаратора относительно нулевого

Comparable contract указывает, что e.compareTo(null) должен бросать NullPointerException.

Из API:

Обратите внимание, что null не является экземпляром какого-либо класса, а e.compareTo(null) должен бросать NullPointerException, хотя e.equals(null) возвращает false.

С другой стороны, Comparator API ничего не говорит о том, что должно произойти при сравнении null. Рассмотрим следующую попытку универсального метода, который принимает Comparable и возвращает a Comparator для него, который ставит null в качестве минимального элемента.

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);
      }
   };
}

Это позволяет нам сделать следующее:

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]"

Итак, вопросы:

  • Это приемлемое использование Comparator, или оно нарушает неписаное правило относительно сравнения null и метания NullPointerException?
  • Не рекомендуется ли даже сортировать List, содержащий элементы null, или это верный признак ошибки проектирования?

Ответ 1

Comparable не позволяет null просто потому, что:

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

для всех объектов a и b где !a.equals(b). Более конкретно:

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)

должен оценивать до true для удовлетворения соответствующих контрактов.

Так что null не разрешено, потому что вы не можете сделать:

null.compareTo(a)

Comparator является более гибким, поэтому обработка null является проблемой, специфичной для реализации. Поддержите это или нет в зависимости от того, что вы хотите сделать Comparator.

Ответ 2

Хорошо ли даже сортировать список, содержащий нулевые элементы, или это верный признак ошибки проектирования?

Концептуально, null означает "ничего", и размещение ничего в списке мне кажется странным. Кроме того, контракт Java List сообщает, что

В некоторых реализациях списков есть ограничения на элементы, которые они могут содержать. Например, некоторые реализации запрещают нулевые элементы

поэтому реализация List в Java даже не требуется для поддержки нулевых элементов вообще. Подводя итог, если у вас нет веской причины помещать нуль в список, не делайте этого, и если вы это сделаете, проверьте, что он действительно работает так, как ожидалось.

Ответ 3

Хорошо ли даже, когда нужно сортировать список, содержащий нулевые элементы, или это верный признак дизайна ошибка?

Хорошо, что для списка не имеет смысла содержать нулевой объект, но, возможно, ваш список содержит "бизнес-объект", и вы можете сортировать по различным свойствам бизнес-объекта, некоторые из которых могут содержать нули.

Это приемлемое использование Компаратор

BeanComparator позволяет сортировать по свойству в бизнес-объекте, даже если свойство содержит null, поэтому я должен был бы сказать это является приемлемым использованием компаратора.