Как упростить нуль-безопасную реализацию compareTo ()? - PullRequest
135 голосов
/ 27 января 2009

Я реализую compareTo() метод для простого класса, такого как этот (чтобы иметь возможность использовать Collections.sort() и другие возможности, предлагаемые платформой Java):

public class Metadata implements Comparable<Metadata> {
    private String name;
    private String value;

// Imagine basic constructor and accessors here
// Irrelevant parts omitted
}

Я хочу, чтобы естественный порядок для этих объектов был: 1) отсортированным по имени и 2) отсортированным по значению, если имя совпадает; оба сравнения должны быть без учета регистра. Для обоих полей нулевые значения вполне приемлемы, поэтому compareTo не должно разрываться в этих случаях.

Решение, которое приходит на ум, заключается в следующем (я использую «охранные предложения» здесь, в то время как другие могут предпочесть одну точку возврата, но это не относится к делу):

// primarily by name, secondarily by value; null-safe; case-insensitive
public int compareTo(Metadata other) {
    if (this.name == null && other.name != null){
        return -1;
    }
    else if (this.name != null && other.name == null){
        return 1;
    }
    else if (this.name != null && other.name != null) {
        int result = this.name.compareToIgnoreCase(other.name);
        if (result != 0){
            return result;
        }
    }

    if (this.value == null) {
        return other.value == null ? 0 : -1;
    }
    if (other.value == null){
        return 1;
    }

    return this.value.compareToIgnoreCase(other.value);
}

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

Вопрос в том, как бы вы сделали это менее многословным (при сохранении функциональности)? Не стесняйтесь обращаться к стандартным библиотекам Java или Apache Commons, если они помогают. Будет ли единственный вариант сделать это (немного) проще - реализовать мой собственный «NullSafeStringComparator» и применить его для сравнения обоих полей?

Правки 1-3 : Эдди прав; исправлена ​​ошибка "оба имени равны нулю"

О принятом ответе

Я задавал этот вопрос еще в 2009 году, конечно, на Java 1.6, и в то время чистое решение JDK от Eddie было моим предпочтительным принятым ответом. Я никогда не удосужился изменить это до сих пор (2017).

Существуют также сторонние решения для библиотек - одна из коллекций Apache Commons 2009 года и одна из них - гуава 2013 года, которые были опубликованы мной, - которые я предпочел в какой-то момент времени.

Теперь я сделал чистый Java 8 от Лукаша Виктора принятый ответ. Это определенно должно быть предпочтительным, если на Java 8, и в наши дни Java 8 должна быть доступна почти для всех проектов.

Ответы [ 16 ]

1 голос
/ 08 марта 2019

Один из простых способов использования с помощью NullSafe Comparator - использовать его реализацию в Spring, ниже приведен один из простых примеров для ссылки:

public int compare(Object o1, Object o2) {
        ValidationMessage m1 = (ValidationMessage) o1;
        ValidationMessage m2 = (ValidationMessage) o2;
        int c;
        if (m1.getTimestamp() == m2.getTimestamp()) {
            c = NullSafeComparator.NULLS_HIGH.compare(m1.getProperty(), m2.getProperty());
            if (c == 0) {
                c = m1.getSeverity().compareTo(m2.getSeverity());
                if (c == 0) {
                    c = m1.getMessage().compareTo(m2.getMessage());
                }
            }
        }
        else {
            c = (m1.getTimestamp() > m2.getTimestamp()) ? -1 : 1;
        }
        return c;
    }
1 голос
/ 13 февраля 2017
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Comparator;

public class TestClass {

    public static void main(String[] args) {

        Student s1 = new Student("1","Nikhil");
        Student s2 = new Student("1","*");
        Student s3 = new Student("1",null);
        Student s11 = new Student("2","Nikhil");
        Student s12 = new Student("2","*");
        Student s13 = new Student("2",null);
        List<Student> list = new ArrayList<Student>();
        list.add(s1);
        list.add(s2);
        list.add(s3);
        list.add(s11);
        list.add(s12);
        list.add(s13);

        list.sort(Comparator.comparing(Student::getName,Comparator.nullsLast(Comparator.naturalOrder())));

        for (Iterator iterator = list.iterator(); iterator.hasNext();) {
            Student student = (Student) iterator.next();
            System.out.println(student);
        }


    }

}

вывод

Student [name=*, id=1]
Student [name=*, id=2]
Student [name=Nikhil, id=1]
Student [name=Nikhil, id=2]
Student [name=null, id=1]
Student [name=null, id=2]
1 голос
/ 19 января 2016

В случае, если кто-то использует Spring, есть класс org.springframework.util.comparator.NullSafeComparator, который делает это и для вас. Просто украсьте свой собственный сопоставимый с этим как это

new NullSafeComparator<YourObject>(new YourComparable(), true)

https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html

0 голосов
/ 12 июля 2016

Для конкретного случая, когда вы знаете, что данные не будут иметь нулевые значения (всегда хорошая идея для строк) и данные действительно большие, вы все равно делаете три сравнения перед тем, как сравнивать значения, уверен, что это ваш случай , вы можете оптимизировать немного. YMMV как читаемый код превосходит незначительную оптимизацию:

        if(o1.name != null && o2.name != null){
            return o1.name.compareToIgnoreCase(o2.name);
        }
        // at least one is null
        return (o1.name == o2.name) ? 0 : (o1.name != null ? 1 : -1);
0 голосов
/ 15 марта 2016

Это моя реализация, которую я использую для сортировки моего ArrayList. нулевые классы сортируются до последнего.

для моего случая EntityPhone расширяет EntityAbstract, а мой контейнер - List .

метод «CompareIfNull ()» используется для безопасной сортировки нуля. Остальные методы приведены для полноты и показывают, как можно использовать compareIfNull.

@Nullable
private static Integer compareIfNull(EntityPhone ep1, EntityPhone ep2) {

    if (ep1 == null || ep2 == null) {
        if (ep1 == ep2) {
            return 0;
        }
        return ep1 == null ? -1 : 1;
    }
    return null;
}

private static final Comparator<EntityAbstract> AbsComparatorByName = = new Comparator<EntityAbstract>() {
    @Override
    public int compare(EntityAbstract ea1, EntityAbstract ea2) {

    //sort type Phone first.
    EntityPhone ep1 = getEntityPhone(ea1);
    EntityPhone ep2 = getEntityPhone(ea2);

    //null compare
    Integer x = compareIfNull(ep1, ep2);
    if (x != null) return x;

    String name1 = ep1.getName().toUpperCase();
    String name2 = ep2.getName().toUpperCase();

    return name1.compareTo(name2);
}
}


private static EntityPhone getEntityPhone(EntityAbstract ea) { 
    return (ea != null && ea.getClass() == EntityPhone.class) ?
            (EntityPhone) ea : null;
}
0 голосов
/ 11 августа 2015

Другой пример Apache ObjectUtils. Умеет сортировать другие типы объектов.

@Override
public int compare(Object o1, Object o2) {
    String s1 = ObjectUtils.toString(o1);
    String s2 = ObjectUtils.toString(o2);
    return s1.toLowerCase().compareTo(s2.toLowerCase());
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...