Вопрос-ответ

How to compare objects by multiple fields

Как сравнивать объекты по нескольким полям

Предположим, у вас есть несколько объектов, у которых есть несколько полей, по которым их можно сравнивать:

public class Person {

private String firstName;
private String lastName;
private String age;

/* Constructors */

/* Methods */

}

Итак, в этом примере, когда вы спрашиваете, если:

a.compareTo(b) > 0

возможно, вы спрашиваете, стоит ли фамилия a перед фамилией b, или a старше, чем b, и т.д...

Какой самый чистый способ включить многократное сравнение между объектами такого типа без добавления ненужного беспорядка или накладных расходов?


  • java.lang.Comparable интерфейс позволяет сравнивать только по одному полю

  • Добавление многочисленных методов сравнения (т.Е. compareByFirstName(), compareByAge() и т.д.), На мой взгляд, перегружено.

Итак, как лучше всего это сделать?

Переведено автоматически
Ответ 1

С Java 8:

Comparator.comparing((Person p)->p.firstName)
.thenComparing(p->p.lastName)
.thenComparingInt(p->p.age);

Если у вас есть методы доступа:

Comparator.comparing(Person::getFirstName)
.thenComparing(Person::getLastName)
.thenComparingInt(Person::getAge);

Если класс реализует Comparable, то такой компаратор может быть использован в методе compareTo:

@Override
public int compareTo(Person o){
return Comparator.comparing(Person::getFirstName)
.thenComparing(Person::getLastName)
.thenComparingInt(Person::getAge)
.compare(this, o);
}
Ответ 2

Вы должны реализовать Comparable <Person>. Предполагая, что все поля не будут равны null (для простоты), что age - это int, а сравнение ранжирования - first, last, age, compareTo метод довольно прост:

public int compareTo(Person other) {
int i = firstName.compareTo(other.firstName);
if (i != 0) return i;

i = lastName.compareTo(other.lastName);
if (i != 0) return i;

return Integer.compare(age, other.age);
}
Ответ 3

(из Способов сортировки списков объектов в Java на основе нескольких полей)

Рабочий код в этой сути

Использование Java 8 lambda (добавлено 10 апреля 2019 г.)

Java 8 прекрасно решает эту проблему с помощью lambda (хотя Guava и Apache Commons все еще могут предлагать большую гибкость):

Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));

Спасибо за ответ @gaoagong ниже.

Беспорядочно и запутанно: сортировка вручную

Collections.sort(pizzas, new Comparator<Pizza>() {  
@Override
public int compare(Pizza p1, Pizza p2) {
int sizeCmp = p1.size.compareTo(p2.size);
if (sizeCmp != 0) {
return sizeCmp;
}
int nrOfToppingsCmp = p1.nrOfToppings.compareTo(p2.nrOfToppings);
if (nrOfToppingsCmp != 0) {
return nrOfToppingsCmp;
}
return p1.name.compareTo(p2.name);
}
});

Это требует большого набора текста, обслуживания и подвержено ошибкам.

Отражающий способ: сортировка с помощью BeanComparator

ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));

Collections.sort(pizzas, chain);

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

Как добраться: сортировка с помощью цепочки сравнений Google Guava

Collections.sort(pizzas, new Comparator<Pizza>() {  
@Override
public int compare(Pizza p1, Pizza p2) {
return ComparisonChain.start().compare(p1.size, p2.size).compare(p1.nrOfToppings, p2.nrOfToppings).compare(p1.name, p2.name).result();
// or in case the fields can be null:
/*
return ComparisonChain.start()
.compare(p1.size, p2.size, Ordering.natural().nullsLast())
.compare(p1.nrOfToppings, p2.nrOfToppings, Ordering.natural().nullsLast())
.compare(p1.name, p2.name, Ordering.natural().nullsLast())
.result();
*/

}
});

Это намного лучше, но требует некоторого кода для наиболее распространенного варианта использования: значения null по умолчанию должны быть меньше. Для нулевых полей вы должны предоставить дополнительную директиву Guava, что делать в этом случае. Это гибкий механизм, если вы хотите сделать что-то конкретное, но часто вам нужен регистр по умолчанию (т.Е. 1, a, b, z, null).

Сортировка с помощью Apache Commons CompareToBuilder

Collections.sort(pizzas, new Comparator<Pizza>() {  
@Override
public int compare(Pizza p1, Pizza p2) {
return new CompareToBuilder().append(p1.size, p2.size).append(p1.nrOfToppings, p2.nrOfToppings).append(p1.name, p2.name).toComparison();
}
});

Подобно ComparisonChain от Guava, этот библиотечный класс легко выполняет сортировку по нескольким полям, но также определяет поведение по умолчанию для нулевых значений (т.Е. 1, a, b, z, null). Однако вы также не можете указать ничего другого, если не предоставите свой собственный компаратор.

Таким образом

В конечном итоге все сводится к вкусу и необходимости гибкости (цепочка сравнений Guava) по сравнению с лаконичным кодом (CompareToBuilder от Apache).

Бонусный метод

Я нашел хорошее решение, которое объединяет несколько компараторов в порядке приоритета в CodeReview в MultiComparator:

class MultiComparator<T> implements Comparator<T> {
private final List<Comparator<T>> comparators;

public MultiComparator(List<Comparator<? super T>> comparators) {
this.comparators = comparators;
}

public MultiComparator(Comparator<? super T>... comparators) {
this(Arrays.asList(comparators));
}

public int compare(T o1, T o2) {
for (Comparator<T> c : comparators) {
int result = c.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}

public static <T> void sort(List<T> list, Comparator<? super T>... comparators) {
Collections.sort(list, new MultiComparator<T>(comparators));
}
}

Конечно, в Apache Commons Collections уже есть утилита для этого:

ComparatorUtils.chainedComparator(comparatorCollection)

Collections.sort(list, ComparatorUtils.chainedComparator(comparators));
Ответ 4

Вы можете реализовать Comparator который сравнивает два Person объекта, и вы можете исследовать столько полей, сколько захотите. Вы можете ввести в свой компаратор переменную, которая сообщает ему, с каким полем сравнивать, хотя, вероятно, было бы проще просто написать несколько компараторов.

java oop