В Java я использую класс, в котором некоторые поля могут быть null. Например:
class Foo {
String bar;
//....
}
Я хочу написать BarComparator для этого класса,
private static class BarComparator
implements Comparator<Foo> {
public int compare( final Foo o1, final Foo o2 )
{
// Implementation goes here
}
}
Есть ли стандартный способ справиться с тем фактом, что любой из o1, o2, o1.bar, o2.bar может быть null, без написания множества вложенных if ... else?
Ваше здоровье!




Я думаю, что ранние операторы возврата были бы другой альтернативой множеству if.
например
if (o1==null) return x;
if (o2==null) return x;
if (o1.getBar()==null) return x;
if (o2.getBar()==null) return x;
// No null checks needed from this point.
Это зависит от того, считаете ли вы пустую запись допустимым строковым значением, достойным сравнения. равно null <или> "яблоко". Единственное, что я могу сказать наверняка, это то, что null == null. Если вы можете определить, где null вписывается в порядок, вы можете написать код соответствующим образом.
В этом случае я мог бы выбрать исключение NullPointerExcpetion или IllegalArgumentException и попытаться обработать значение null на более высоком уровне, не помещая его в сравнение в первую очередь.
Я думаю, вы могли бы обернуть вызов метода поля compareTo небольшим статическим методом для сортировки нулей по высоким или низким значениям:
static <T extends Comparable<T>> int cp(T a, T b) {
return
a==null ?
(b==null ? 0 : Integer.MIN_VALUE) :
(b==null ? Integer.MAX_VALUE : a.compareTo(b));
}
Простое использование (несколько полей, как обычно):
public int compare( final Foo o1, final Foo o2 ) {
return cp(o1.field, o2.field);
}
Не по теме я знаю, но в чем причина предпочесть MIN / MAX_VALUE, а не - / + 1?
Приносим извинения за задержку ответа. Это необходимо для того, чтобы у нас было неравенство треугольника. Для a> b> c, a.compareTo (b) + b.compareTo (c) <= a.compareTo (c). Не то чтобы никого это волновало ...
По-прежнему может выдать NullPointerException, если o1 / o2 равно нулю. Как следует относиться к нулевым значениям o1 / o2? Упомянутый OP: o1, o2, o1.bar, o2.bar может быть нулевым. Или это часть контракта компаратора: сравнение нулей должно вызывать NPE?
@Daniel Comparator.compare может генерировать NPE с аргументами null. Как правило, лучше всего как можно раньше отлавливать любые ложные нули или любую другую форму недопустимого аргумента (хотя я отмечаю, что в исходном вопросе упоминается o1, а o2 может быть null).
Ключевым моментом здесь является определение того, как вы хотите, чтобы значения NULL обрабатывались. Некоторые варианты: a) предполагать, что нули идут раньше всех других объектов в порядке сортировки; b) предполагать, что нули идут после всех других объектов в порядке сортировки; c) рассматривать null как эквивалент некоторого значения по умолчанию; d) рассматривать нули как условия ошибки. Какой из них вы выберете, полностью зависит от приложения, над которым вы работаете.
В последнем случае вы, конечно, генерируете исключение. Для других вам нужен четырехсторонний случай if / else (примерно за три минуты кодирования вы уже выработали, какими должны быть результаты).
Если вы используете коллекции Google, вам может быть полезен класс Компараторы. Если имеет вспомогательные методы для упорядочивания нулей как наибольших или наименьших элементов в коллекции. Вы можете использовать составные компараторы, чтобы уменьшить количество кода.
Спасибо за ответы! Общий метод и компараторы Google выглядят интересно.
И я обнаружил, что в Коллекции Apache Commons (который мы сейчас используем) есть NullComparator:
private static class BarComparator
implements Comparator<Foo>
{
public int compare( final Foo o1, final Foo o2 )
{
// o1.bar & o2.bar nulleness is taken care of by the NullComparator.
// Easy to extend to more fields.
return NULL_COMPARATOR.compare(o1.bar, o2.bar);
}
private final static NullComparator NULL_COMPARATOR =
new NullComparator(false);
}
Примечание: я сосредоточился здесь на поле bar, чтобы не упустить его.
Похоже, ссылка на Javadocs мертва. Теперь он находится по адресу commons.apache.org/proper/commons-collections/javadocs/….
Вы не должны использовать NullComparator так, как вы это делаете - вы создаете новый экземпляр класса для каждой операции сравнения, и если, например, вы сортируете список с 1000 записями, это будет 1000 * log2 (1000) объектов, которые совершенно лишние. Это может быстро стать проблемой.
Либо подклассифицируйте его, либо делегируйте ему, либо просто реализуйте свою собственную нулевую проверку - это действительно не так сложно:
private static class BarComparator
implements Comparator<Foo> {
private NullComparator delegate = new NullComparator(false);
public int compare( final Foo o1, final Foo o2 )
{
return delegate.compare(o1.bar, o2.bar);
}
}
Вы правы, NullComparator должен быть частным статическим полем. Я написал это таким образом в примере, чтобы сосредоточиться на ничтожности.
Вы можете написать для него свой компаратор. Допустим, у вас есть класс Person со строковым именем в качестве частного поля. getName () и setName () для доступа к имени поля. Ниже приведен компаратор для класса Person.
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person a, Person b) {
if (a == null) {
if (b == null) {
return 0;
}
return -1;
} else if (b == null) {
return 1;
}
return a.getName().compareTo(b.getName());
}
});
Обновлять:
Начиная с Java 8, вы можете использовать нижеприведенные API для списка.
// Push nulls at the end of List
Collections.sort(subjects1, Comparator.nullsLast(String::compareTo));
// Push nulls at the beginning of List
Collections.sort(subjects1, Comparator.nullsFirst(String::compareTo));
В Spring Framework также есть класс org.springframework.util.comparator.NullSafeComparator, который вы можете использовать.
Пример (Java 8):
SortedSet<Foo> foos = new TreeSet<>( ( o1, o2 ) -> {
return new NullSafeComparator<>( String::compareTo, true ).compare( o1.getBar(), o2.getBar() );
} );
foos.add( new Foo(null) );
foos.add( new Foo("zzz") );
foos.add( new Foo("aaa") );
foos.stream().forEach( System.out::println );
Это напечатает:
Foo{bar='null'}
Foo{bar='aaa'}
Foo{bar='zzz'}
Рассматривая Клиента как POJO, я бы ответил:
Comparator<Customer> compareCustomer = Comparator.nullsLast((c1,c2) -> c1.getCustomerId().compareTo(c2.getCustomerId()));
Или же
Comparator<Customer> compareByName = Comparator.comparing(Customer::getName,nullsLast(String::compareTo));
Мне нравится этот ответ. Спасибо!