(od sposobów sortowania list obiektów w Javie na podstawie wielu pól )
Działający kod w tej treści
Korzystanie z Java 8 lambda (dodano 10 kwietnia 2019 r.)
Java 8 ładnie rozwiązuje to przez lambda (chociaż Guava i Apache Commons mogą nadal oferować większą elastyczność):
Collections.sort(reportList, Comparator.comparing(Report::getReportKey)
.thenComparing(Report::getStudentNumber)
.thenComparing(Report::getSchool));
Dzięki odpowiedzi @ gaoagong poniżej .
Bałagan i zawiłości: sortowanie ręczne
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);
}
});
Wymaga to dużo pisania, konserwacji i jest podatne na błędy.
Refleksyjny sposób: sortowanie za pomocą BeanComparator
ComparatorChain chain = new ComparatorChain(Arrays.asList(
new BeanComparator("size"),
new BeanComparator("nrOfToppings"),
new BeanComparator("name")));
Collections.sort(pizzas, chain);
Oczywiście jest to bardziej zwięzłe, ale jeszcze bardziej podatne na błędy, ponieważ tracisz bezpośrednie odniesienie do pól za pomocą Ciągów (zamiast bezpieczeństwa typów, auto-refaktoryzacji). Teraz, jeśli zmieniono nazwę pola, kompilator nawet nie zgłosi problemu. Ponadto, ponieważ w tym rozwiązaniu wykorzystuje się odbicie, sortowanie jest znacznie wolniejsze.
Jak się tam dostać: Sortowanie za pomocą Google Guava's CompareChain
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();
*/
}
});
Jest to o wiele lepsze, ale wymaga trochę kodu płyty kotłowej dla najczęstszego przypadku użycia: wartości zerowe powinny być domyślnie mniej cenione. W przypadku pól zerowych musisz podać Guava dodatkową dyrektywę, co robić w takim przypadku. Jest to elastyczny mechanizm, jeśli chcesz zrobić coś konkretnego, ale często chcesz domyślny przypadek (np. 1, a, b, z, null).
Sortowanie za pomocą 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();
}
});
Podobnie jak Guava's CompareChain, ta klasa biblioteki łatwo sortuje według wielu pól, ale także definiuje domyślne zachowanie dla wartości zerowych (tj. 1, a, b, z, null). Nie możesz jednak podać niczego innego, chyba że podasz własny komparator.
A zatem
Ostatecznie sprowadza się to do smaku i potrzeby elastyczności (Guava's CompareChain) vs. zwięzłego kodu (Apache's CompareToBuilder).
Metoda bonusowa
Znalazłem dobre rozwiązanie, które łączy wiele komparatorów w kolejności pierwszeństwa na CodeReview w 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));
}
}
Ofcourse Apache Commons Collections ma już do tego zastosowanie:
ComparatorUtils.chainedComparator (ComparatorCollection)
Collections.sort(list, ComparatorUtils.chainedComparator(comparators));