Implementuję compareTo()
metodę dla prostej klasy, takiej jak ta (aby móc korzystać z Collections.sort()
innych dobrodziejstw oferowanych przez platformę Java):
public class Metadata implements Comparable<Metadata> {
private String name;
private String value;
// Imagine basic constructor and accessors here
// Irrelevant parts omitted
}
Chcę, aby te obiekty były uporządkowane w sposób naturalny : 1) posortowane według nazwy i 2) posortowane według wartości, jeśli nazwa jest taka sama; w obu porównaniach nie należy rozróżniać wielkości liter. Dla obu pól wartości null są całkowicie akceptowalne, więc compareTo
w takich przypadkach nie wolno ich łamać.
Rozwiązanie, które przychodzi na myśl, wygląda następująco (używam tutaj „klauzul ochronnych”, podczas gdy inni mogą preferować pojedynczy punkt powrotu, ale to nie ma znaczenia):
// 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);
}
To działa, ale nie jestem do końca zadowolony z tego kodu. Wprawdzie nie jest to zbyt skomplikowane, ale jest dość rozwlekłe i żmudne.
Pytanie brzmi, jak uczynić to mniej szczegółowym (zachowując funkcjonalność)? Jeśli pomogą, możesz odwołać się do standardowych bibliotek Java lub Apache Commons. Czy jedyną opcją, aby to (trochę) uprościć, byłoby zaimplementowanie własnego „NullSafeStringComparator” i zastosowanie go do porównania obu pól?
Edycje 1-3 : Eddie ma rację; naprawiono powyższy przypadek "obie nazwy są puste"
O przyjętej odpowiedzi
Zadałem to pytanie w 2009 roku, oczywiście na Javie 1.6, a wtedy czyste rozwiązanie JDK autorstwa Eddiego było moją preferowaną akceptowaną odpowiedzią. Nigdy nie udało mi się tego zmienić aż do teraz (2017).
Istnieją również rozwiązania biblioteczne innych firm - 2009 Apache Commons Collections i 2013 Guava, oba opublikowane przeze mnie - które wolałem w pewnym momencie.
Teraz zaakceptowałem odpowiedź z czystego rozwiązania Java 8 autorstwa Łukasza Wiktora . Powinno to być zdecydowanie preferowane, jeśli jest to Java 8, a obecnie Java 8 powinna być dostępna dla prawie wszystkich projektów.
źródło
Odpowiedzi:
Korzystanie z języka Java 8 :
źródło
Collections.sort(List)
to nie działa, gdy lista zawiera wartości null, komentarz nie dotyczy pytania.Możesz po prostu użyć Apache Commons Lang :
źródło
nullsFirst()
/nullsLast()
.org.apache.commons.lang3
) jest „starsze / źle utrzymane / niskiej jakości” jest fałszywe lub w najlepszym przypadku bezpodstawne. Commons Lang3 jest łatwe do zrozumienia i użytkowania oraz jest aktywnie utrzymywane. Jest to prawdopodobnie moja najczęściej używana biblioteka (poza Spring Framework i Spring Security) - na przykład klasa StringUtils z metodami bezpiecznymi dla wartości null sprawia, że normalizacja wejścia jest banalna.Zaimplementowałbym zerowy bezpieczny komparator. Może istnieć implementacja, ale jest to tak proste do zaimplementowania, że zawsze tworzyłem własną.
Uwaga: powyższy komparator, jeśli obie nazwy są puste, nie porówna nawet pól wartości. Nie sądzę, że tego chcesz.
Zaimplementowałbym to za pomocą czegoś takiego:
EDYCJA: Poprawiono literówki w próbce kodu. Właśnie to dostaję, nie testując go najpierw!
EDYCJA: Promowany nullSafeStringComparator do statycznego.
źródło
final
słowo kluczowe nie jest naprawdę potrzebne (kod Java jest już gadatliwy). Jednak zapobiega ponownemu wykorzystywaniu parametrów jako lokalnych zmiennych (okropna praktyka kodowania). nasze zbiorowe rozumienie oprogramowania staje się lepsze z biegiem czasu, wiemy, że domyślnie rzeczy powinny być ostateczne / stałe / niezmienne. Więc wolę trochę więcej gadatliwości w używaniufinal
w deklaracjach parametrów (jakkolwiek trywialna może być ta funkcja) do uzyskaniainmutability-by-quasi-default
.) Narzut związany z zrozumiałością / utrzymywalnością jest pomijalny w ogólnym schemacie rzeczy.Zobacz dolną część tej odpowiedzi dla zaktualizowanego (2013) rozwiązania używającego guawy.
Z tym ostatecznie poszedłem. Okazało się, że mamy już metodę narzędziową do porównywania ciągów znaków null-safe, więc najprostszym rozwiązaniem było skorzystanie z tego. (To duża baza kodów; łatwo przeoczyć takie rzeczy :)
Oto jak jest zdefiniowany helper (jest przeciążony, więc możesz również zdefiniować, czy wartości null są pierwsze czy ostatnie, jeśli chcesz):
Jest to więc zasadniczo to samo, co odpowiedź Eddiego (chociaż statycznej metody pomocniczej nie nazwałbym komparatorem ), a także uzhin .
W każdym razie, ogólnie rzecz biorąc, zdecydowanie wolałbym rozwiązanie Patricka , ponieważ uważam, że dobrą praktyką jest korzystanie z istniejących bibliotek, gdy tylko jest to możliwe. ( Poznaj i korzystaj z bibliotek, jak mówi Josh Bloch.) Ale w tym przypadku nie dałoby to najczystszego, najprostszego kodu.
Edycja (2009): wersja Apache Commons Collections
Właściwie, oto sposób na
NullComparator
uproszczenie rozwiązania opartego na Apache Commons . Połącz to z rozróżnianiem wielkości literComparator
wString
klasie:Myślę, że to całkiem eleganckie. (Pozostaje tylko jeden mały problem: Commons
NullComparator
nie obsługuje typów ogólnych, więc istnieje niezaznaczone przypisanie).Aktualizacja (2013): wersja Guava
Prawie 5 lat później, oto jak poradziłbym sobie z moim pierwotnym pytaniem. Jeśli koduję w Javie, użyłbym (oczywiście) guawy . (I na pewno nie Apache Commons).
Umieść tę stałą gdzieś, np. W klasie „StringUtils”:
Następnie w
public class Metadata implements Comparable<Metadata>
:Oczywiście jest to prawie identyczne z wersją Apache Commons (obie używają CASE_INSENSITIVE_ORDER JDK ), ponieważ
nullsLast()
jest to jedyna rzecz specyficzna dla guawy. Ta wersja jest lepsza po prostu dlatego, że Guava jest lepsza, jako zależność, od Commons Collections. (Jak wszyscy się zgadzają .)Jeśli się zastanawiałeś
Ordering
, zwróć uwagę, że implementujeComparator
. Jest to bardzo przydatne, szczególnie w przypadku bardziej złożonych potrzeb związanych z sortowaniem, pozwalając na przykład połączyć kilka Zamówień za pomocącompound()
. Przeczytaj wyjaśnienie dotyczące zamawiania, aby uzyskać więcej informacji!źródło
ComparatorChain
więc nie potrzebujesz własnejcompareTo
metody.Zawsze polecam korzystanie ze wspólnych zasobów Apache, ponieważ najprawdopodobniej będzie to lepsze niż takie, które możesz pisać samodzielnie. Dodatkowo możesz wtedy wykonywać „prawdziwą” pracę, zamiast wymyślać na nowo.
Klasa, którą jesteś zainteresowany, to komparator wartości zerowych . Pozwala tworzyć wartości zerowe wysokie lub niskie. Dajesz mu również swój własny komparator, który ma być używany, gdy dwie wartości nie są zerowe.
W twoim przypadku możesz mieć statyczną zmienną składową, która dokonuje porównania, a następnie twoja
compareTo
metoda odwołuje się do niej.Coś jak
}
Nawet jeśli zdecydujesz się wyrzucić własną, pamiętaj o tej klasie, ponieważ jest bardzo przydatna podczas zamawiania list, które zawierają elementy puste.
źródło
Wiem, że może to nie być bezpośrednią odpowiedzią na twoje pytanie, ponieważ powiedziałeś, że muszą być obsługiwane wartości zerowe.
Ale chcę tylko zauważyć, że obsługa wartości null w funkcji compareTo nie jest zgodna z umową compareTo opisaną w oficjalnych javadocs dla Comparable :
Więc albo wyrzuciłbym jawnie NullPointerException, albo po prostu pozwoliłbym, aby został wyrzucony po raz pierwszy, gdy dereferencjonowany jest argument o wartości null.
źródło
Możesz wyodrębnić metodę:
}
źródło
Możesz zaprojektować swoją klasę tak, aby była niezmienna (Effective Java 2nd Ed. Ma świetną sekcję na ten temat, Punkt 15: Minimalizuj zmienność) i upewnić się podczas konstrukcji, że żadne wartości null nie są możliwe (i użyj wzorca obiektu zerowego, jeśli to konieczne). Następnie możesz pominąć wszystkie te sprawdzenia i bezpiecznie założyć, że wartości nie są zerowe.
źródło
Szukałem czegoś podobnego i wydawało mi się to trochę skomplikowane, więc zrobiłem to. Myślę, że jest to trochę łatwiejsze do zrozumienia. Możesz go używać jako komparatora lub jako pojedynczej wkładki. W przypadku tego pytania należy zmienić na compareToIgnoreCase (). Tak jak jest, unoszą się wartości zerowe. Możesz odwrócić 1, -1, jeśli chcesz, aby zatonęły.
.
źródło
możemy użyć java 8, aby dokonać przyjaznego zeru porównania między obiektem. przypuszczam, że mam klasę Boy z 2 polami: nazwa łańcucha i całkowity wiek i chcę najpierw porównać imiona, a następnie wiek, jeśli oba są równe.
a wynik:
źródło
Jeśli ktoś używa Springa, istnieje klasa org.springframework.util.comparator.NullSafeComparator, która również zrobi to za Ciebie. Po prostu udekoruj swój własny porównywalny z tym w ten sposób
new NullSafeComparator<YourObject>(new YourComparable(), true)
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/comparator/NullSafeComparator.html
źródło
W konkretnym przypadku, w którym wiesz, że dane nie będą miały wartości null (zawsze jest to dobry pomysł w przypadku ciągów znaków), a dane są naprawdę duże, nadal wykonujesz trzy porównania przed faktycznym porównaniem wartości, jeśli wiesz na pewno, że tak jest w Twoim przypadku , możesz trochę zoptymalizować. YMMV jako czytelny kod przewyższa drobną optymalizację:
źródło
wyjście jest
źródło
Jednym z prostych sposobów korzystania z komparatora NullSafe jest użycie implementacji Springa, poniżej znajduje się jeden z prostych przykładów do odniesienia:
źródło
Kolejny przykład Apache ObjectUtils. Potrafi sortować inne typy obiektów.
źródło
To jest moja implementacja, której używam do sortowania mojej ArrayList. klasy puste są sortowane do ostatniej.
w moim przypadku EntityPhone rozszerza EntityAbstract, a mój kontener to List <EntityAbstract>.
metoda "compareIfNull ()" jest używana do sortowania z zabezpieczeniem zerowym. Inne metody służą do określania kompletności, pokazując, w jaki sposób można użyć compareIfNull.
źródło
Jeśli chcesz prostego hacka:
jeśli chcesz wstawić wartości null na koniec listy, po prostu zmień to w powyższej metodzie
źródło