Załóżmy, że masz kilka obiektów o kilku polach, które można porównać:
public class Person {
private String firstName;
private String lastName;
private String age;
/* Constructors */
/* Methods */
}
W tym przykładzie, kiedy pytasz, czy:
a.compareTo(b) > 0
możesz zapytać, czy nazwisko a występuje przed b, czy a jest starsze niż b itp.
Jaki jest najczystszy sposób na wielokrotne porównywanie tego rodzaju obiektów bez niepotrzebnego bałaganu lub narzutu?
java.lang.Comparable
interfejs umożliwia porównanie tylko przez jedno pole- Dodając liczne porównania metod (tj
compareByFirstName()
,compareByAge()
itp ...) jest bałagan w mojej opinii.
Więc jak najlepiej to zrobić?
Odpowiedzi:
Możesz zaimplementować obiekt
Comparator
porównujący dwaPerson
obiekty i zbadać dowolną liczbę pól. Możesz wprowadzić zmienną w swoim komparatorze, która mówi mu, z którym polem ma się porównać, chociaż prawdopodobnie łatwiej byłoby po prostu napisać wiele komparatorów.źródło
W Javie 8:
Jeśli masz metody akcesora:
Jeśli klasa implementuje Porównywalny, taki komparator może być użyty w metodzie CompareTo:
źródło
(Person p)
jest ważna dla komparatorów łańcuchowych.Comparator
wystąpienia w każdym połączeniu?.thenComparing(Person::getLastName, Comparator.nullsFirst(Comparator.naturalOrder()))
- pierwszy selektor pola, a następnie komparatorcompareTo
jak pokazano powyżej,Comparator
jest tworzony za każdym razem, gdy wywoływana jest metoda. Można temu zapobiec, przechowując komparator w prywatnym statycznym polu końcowym.Powinieneś wdrożyć
Comparable <Person>
. Zakładając, że wszystkie pola nie będą miały wartości zerowej (dla uproszczenia), że wiek jest liczbą całkowitą i porównanie rankingu jest pierwsze, ostatnie, wiek,compareTo
metoda jest dość prosta:źródło
(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ść):
Dzięki odpowiedzi @ gaoagong poniżej .
Bałagan i zawiłości: sortowanie ręczne
Wymaga to dużo pisania, konserwacji i jest podatne na błędy.
Refleksyjny sposób: sortowanie za pomocą BeanComparator
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
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
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
:Ofcourse Apache Commons Collections ma już do tego zastosowanie:
ComparatorUtils.chainedComparator (ComparatorCollection)
źródło
@Patrick Aby posortować więcej niż jedno pole, spróbuj po prostu ComparatorChain
źródło
Inną opcją, którą zawsze możesz rozważyć, jest Apache Commons. Zapewnia wiele opcji.
Dawny:
źródło
Możesz także zajrzeć na Enum, który implementuje Komparator.
http://tobega.blogspot.com/2008/05/beautiful-enums.html
na przykład
źródło
źródło
Dla tych, którzy potrafią korzystać z interfejsu API przesyłania strumieniowego Java 8, istnieje dobrze udokumentowane podejście, które zostało dobrze udokumentowane: lambda i sortowanie
Szukałem odpowiednika C # LINQ:
Znalazłem mechanizm w Javie 8 na Komparatorze:
Oto fragment kodu demonstrujący algorytm.
Sprawdź powyższy link, aby uzyskać bardziej przejrzysty sposób i wyjaśnienie, w jaki sposób wnioskowanie o typie Javy sprawia, że definiowanie go jest nieco bardziej niezręczne w porównaniu z LINQ.
Oto pełny test jednostkowy w celach informacyjnych:
źródło
Comparator
Ręczne pisanie dla takiego przypadku użycia jest okropnym rozwiązaniem IMO. Takie podejścia ad hoc mają wiele wad:Więc jakie jest rozwiązanie?
Najpierw trochę teorii.
Oznaczmy propozycję „typ
A
obsługuje porównanie” przezOrd A
. (Z perspektywy programu można myślećOrd A
o obiekcie zawierającym logikę do porównywania dwóchA
s. Tak, tak jakComparator
.)Teraz, jeśli
Ord A
iOrd B
, to ich kompozyt(A, B)
powinien również obsługiwać porównanie. tjOrd (A, B)
. JeśliOrd A
,Ord B
iOrd C
wtedyOrd (A, B, C)
.Możemy rozszerzyć ten argument na dowolne arsenał i powiedzieć:
Ord A, Ord B, Ord C, ..., Ord Z
⇒Ord (A, B, C, .., Z)
Nazwijmy to stwierdzenie 1.
Porównanie kompozytów będzie działać tak, jak opisano w pytaniu: pierwsze porównanie zostanie najpierw wypróbowane, następnie następne, następne i tak dalej.
To pierwsza część naszego rozwiązania. Teraz druga część.
Jeśli wiesz, że
Ord A
i wiem jak przekształcićB
doA
(wywołanie tej funkcji transformacjif
), to można równieżOrd B
. W jaki sposób? Cóż, gdyB
porównamy te dwa wystąpienia, najpierw przekształcamy je wA
użycie,f
a następnie stosujemyOrd A
.Tutaj mapujemy transformację
B → A
naOrd A → Ord B
. Jest to znane jako mapowanie przeciwstawne (lubcomap
w skrócie).Ord A, (B → A)
⇒ comapOrd B
Nazwijmy to stwierdzenie 2.
Teraz zastosujmy to do twojego przykładu.
Masz nazwany typ danych,
Person
który obejmuje trzy pola typuString
.Wiemy, że
Ord String
. Przez oświadczenie 1Ord (String, String, String)
.Możemy łatwo napisać funkcję od
Person
do(String, String, String)
. (Zwróć tylko trzy pola.) Ponieważ wiemyOrd (String, String, String)
iPerson → (String, String, String)
poprzez oświadczenie 2, możemy użyć,comap
aby uzyskaćOrd Person
.CO BYŁO DO OKAZANIA.
Jak wdrożyć wszystkie te koncepcje?
Dobra wiadomość jest taka, że nie musisz. Istnieje już biblioteka która implementuje wszystkie pomysły opisane w tym poście. (Jeśli jesteś ciekawy, jak są one realizowane, możesz zajrzeć pod maską .)
Tak będzie z tym wyglądał kod:
Wyjaśnienie:
stringOrd
jest obiektem typuOrd<String>
. Odpowiada to naszej oryginalnej propozycji „obsługuje porównanie”.p3Ord
Jest to metoda, która trwaOrd<A>
,Ord<B>
,Ord<C>
i powrotyOrd<P3<A, B, C>>
. Odpowiada to stwierdzeniu 1. (P3
oznacza produkt z trzema elementami. Produkt jest algebraicznym terminem określającym kompozyty).comap
odpowiada dobrze,comap
.F<A, B>
reprezentuje funkcję transformacjiA → B
.p
to fabryczna metoda tworzenia produktów.Mam nadzieję, że to pomaga.
źródło
Zamiast metod porównawczych możesz po prostu zdefiniować kilka typów podklas „Komparator” w klasie Person. W ten sposób możesz przekazać je do standardowych metod sortowania kolekcji.
źródło
Myślę, że byłoby bardziej mylące, gdyby Twój algorytm porównania był „sprytny”. Wybrałbym wiele zaproponowanych metod porównania.
Jedynym wyjątkiem dla mnie byłaby równość. Przy testowaniu jednostkowym przydało mi się przesłonić .Equals (w .net) w celu ustalenia, czy kilka pól jest równych między dwoma obiektami (a nie, że referencje są równe).
źródło
Jeśli istnieje wiele sposobów, w jakie użytkownik może zamówić osobę, możesz także mieć gdzieś wiele ustawień Komparatora jako stałych. Większość operacji sortowania i sortowanych kolekcji przyjmuje parametr jako parametr.
źródło
źródło
Implementacja tego samego kodu jest tutaj, jeśli musimy posortować obiekt Person na podstawie wielu pól.
źródło
źródło
Jeśli wdrożysz porównywalny interfejs , będziesz chciał wybrać jedną prostą właściwość do zamówienia. Nazywa się to porządkiem naturalnym. Pomyśl o tym jako o wartości domyślnej. Jest zawsze używany, gdy nie ma określonego komparatora. Zwykle jest to nazwa, ale twój przypadek użycia może wymagać czegoś innego. Możesz swobodnie korzystać z dowolnej liczby innych Komparatorów, które możesz dostarczyć do różnych interfejsów API do kolekcji, aby zastąpić naturalne uporządkowanie.
Zauważ też, że zazwyczaj jeśli a.compareTo (b) == 0, to a.equals (b) == true. Jest ok, jeśli nie, ale są skutki uboczne, o których należy pamiętać. Zobacz doskonałe javadoki w interfejsie porównywalnym, a znajdziesz wiele świetnych informacji na ten temat.
źródło
Po blogu podano dobry przykład komparatora
http://www.codejava.net/java-core/collections/sorting-a-list-by-multiple-attributes-example
Komponent wywołujący:
źródło
Począwszy od odpowiedzi Steve'a, operator trójskładnikowy może być użyty:
źródło
Łatwo jest porównać dwa obiekty metodą hashcode w java`
źródło
Zwykle zastępuję moje
compareTo()
metodę w ten sposób, gdy muszę przeprowadzić wielopoziomowe sortowanie.Tutaj pierwszeństwo ma nazwa filmu, następnie wykonawca, a na koniec songLength. Musisz tylko upewnić się, że mnożniki są wystarczająco odległe, aby nie przekraczać wzajemnie swoich granic.
źródło
Łatwo to zrobić za pomocą biblioteki Google Guava .
na przykład
Objects.equal(name, name2) && Objects.equal(age, age2) && ...
Więcej przykładów:
źródło