Kolekcje w Guavie I – Ordering, czyli komparatory po nowemu
Jak wspomniałem w pierwszym tekście o guavie w tym cyklu najczęściej wykorzystywaną funkcjonalnością tej biblioteki jest ta związana z obsługą kolekcji. Nie ma się czemu dziwi ponieważ Guava jako taka wyrosła z Google Collections.
Dziś przyszedł czas na pierwszy tekst poświęcony pracy z kolekcjami w Guavie. Przyjrzymy się klasie Ordering, czyli guavowej „implementacji” interfejsu Comparator.
Wartość dodana
Jak wiadomo Java jest takim śmiesznym językiem, który ma wiele fajnych rozwiązań, ale zazwyczaj brakuje mu „tego czego” co jest potrzebne i co trzeba w końcu samemu wyrzeźbić. Tak jest w przypadku Optional, będących odpowiedzią na brak wartości domyślnej parametru, tak jest z funkcjami, które „naprawiają” brak domknięć, itd.
W przypadku Ordering główna motywacją do użycia jest dostarczenie kilku podstawowych metod pracujących na interfejsie Comparator, a przydatnych przy pracy z kolekcjami.
Rodzaje Ordering
Sama klasa Ordering jest abstrakcyjna i od klas dziedziczących wymaga implementacji tylko jednej metody – compare pochodzącej z interfejsu Comparator. Jeżeli zatem chcemy użyć tej klasy na takiej samej zasadzie jak zwykłego komparatora odpuśćmy sobie. Nie ma to większego sensu. Względnie jeżeli zależy nam na ujednoliceniu kodu możemy wykorzystać metodę natural w celu stworzenia obiektu dla klasy implementującej Comparable tak jak na listingu 1.
Listing 1. Tworzenie naturalnego komparatora
public class OrderingExamples {
public static void main(String[] args) {
Ordering<Name> forName = Ordering.natural();
// not complie - Mongy is not Comparable
Ordering<Mongy> forMongy = Ordering.natural();
}
}
class Name implements Comparable<Name>{
public final String value;
Name(String value) {
this.value = value;
}
@Override
public int compareTo(Name o) {
return value.compareTo(o.value);
}
}
class Mongy{
public final String value;
Mongy(String value) {
this.value = value;
}
}
Próba stworzenia obiektu dla klasy Mongy nie przejdzie nawet kompilacji. Można to oczywiście spróbować obejść wykorzystując „pseudo naturalną” kolejność opartą o metodę toString:
Listing 2. Tworzenie komparatora opartego o toString
class Mongy {
public final String value;
Mongy(String value) {
this.value = value;
}
@Override
public String toString() {
return "Mongy{" +
"value='" + value + '\'' +
'}';
}
}
//...
Ordering<Object> forMongy = Ordering.usingToString();
W tym przypadku możemy mówić o stworzeniu swoistej „protezy”, dla obiektów nieporównywalnych, która pozwoli na na uogólnienie kodu… na przykład w funkcjach… znowu eliminujemy efekty uboczne… To zresztą nie jest istotne. Jeżeli przyjrzymy się innym implementacjom tej klasy
Metoda allEqual
Dostarcza nam komparator traktujący wszystkie obiekty jako równe sobie… powinno się posiadać specjalną zmienną globalną w kodzie COMUNISM by trzymać tam ten obiekt..
Metoda arbitrary
W tym przypadku tego komparatora obiekty są równe jeżeli są tym samym, operator ==. Jeżeli prawy jest null zwracane jest 1 jeżeli lewy -1. Jeżeli to nie daje rady porównywane są wyniki System.identityHashCode(metoda zwraca rezultat hashCode, ale taki jaki byłby gdyby nie nadpisano tej metody). Jeżeli to zawiedzie… cóż… idź puść lotka bo masz farta.
Metoda compound
Ten komparator będzie już bardziej cwany niż koledzy. W momencie gdy porównanie zwróci 0 odpalany jest „komparator drugiej szansy”. Argumentem jest zbior komparatorów odpalanych jeden po drugim w celu osiągnięcia jakiegoś niezerowego rezultatu. Tu przykład na komparator złożony nasuwa się samemu.
Metoda explicit
Tu sprawa jest prosta. Przekazujemy już uporządkowaną kolekcję i na jej podstawie dokonywane jest porównanie.
Metoda from
Tworzy obiekt klasy Ordering na bazie istniejącego komparatora.
Rozbudowa komparatorów
Stworzenie komparatora, nawet trochę bardziej złożonego nie jest niczym szczególnym. Z powyższej listy na uwagę zasługuje tylko „łańcuchowanie” komparatorów ponieważ pisanie tego z palucha jest kapkę upierdliwe, ale się da. Guava pozwala na bardziej przyjazne konfigurowanie komparatorów.
Metoda compound
Podobna do omówionej wcześniej, ale nie statyczna i pozwalająca na zastosowanie „komparatora drugiej szansy” w stosunku do istniejącego już rozwiązania. Dobra sprawa w połączeniu z komparatorem tworzonym za pomocą arbitrary w przetwarzaniu funkcyjnym.
Metoda lexicographical
Rzecz z tym komparatorem opiera się o dziwaczną nazwę. Generalnie porównywanie leksykalne, bo nie słownikowe czy językowe, oznacza porównanie wg. długości. Jak to działa. Przekazujemy dwa iterowalne zbiory. Większy jest zbiór dłuższy. Jeżeli zbiory są równe to porównywane są kolejne elementy aż do wystąpienia wyniku innego niż 0. Jeżeli zbiory są równe i wszystkie ich elementy w kolejności też są równe to zwracamy 0. Przy czym jest to zmyłka, bo nie jest to komparator słownikowy i na przykład próba porównania tablicy stringów [1, 0] i [2] da nieoczekiwany wynik.
Metody nullsFirst i nullsLast
Wzbogacają nasz komparator o obsługę null w sposób „przewidywalny”.
Metoda onResultOf
To jest naprawdę fajna metoda. Tworzy nam nowy komparator, który przed porównaniem obiektów zaaplikuje im funkcję i porówna wyniki. Pozwala to na zbudowanie komparatora dla danego typu o ile tylko mamy funkcję, która zmieni nam inny typ do którego posiadamy komparator na oczekiwany. Zawsze trochę mniej roboty, a i jakoś tak fajnie to wygląda w kodzie.
Metoda reverse
Metoda zwraca komparator odwrotny.
Mamy Ordering i co dalej
Dotychczas opisując sposoby tworzenia i konfigurowania poprzez komparator rozumieliśmy obiekt klasy Ordering. Jak już go mamy to czas najwyższy zobaczyć co on może i czego nei musimy implementować na własną rękę.
Metoda binarySearch
Wywołuje metodę z klasy Collections o tej samej nazwie. Jako trzeci argument, komparator, przekazuje siebie tzn. obiekt klasy Ordering.
Metoda greatestOf
Metoda zwraca K największych obiektów w zbiorze w kolejności od największego.
Metoda leastOf
Metoda zwraca K najmniejszych obiektów w zbiorze w kolejności od największego. Czyli bez rewelacji jak na razie…
Metoda immutableSortedCopy
Otrzymujemy posortowaną, niezmienną kopię oryginalnego zbioru. Oznacza to, że nie jest to widok podatny na zmiany oryginalnego zbioru. Oznacza to też, że można się tu przejechać robiąc memoryleaka w postaci dodatkowych referencji w nowej liście…
Metody isOrdered i isStrictlyOrdered
Pierwsza z nich sprawdza tylko czy zbiór został posortowany (nMetody min i max
Ok… to wiadomo…
Metoda sortedCopy
Posortowana kopia… co tu dużo mówić…
Podsumowanie
Nie wygląda to jakoś super bogato, ale nie zapominajmy, że dzięki tej klasie możemy uniknąć pisania duperelnego kodu w stylu n-największych/najmniejszych, pierwsze nulle, ale tylko w tym miejscu itp. Klasa warta zastosowania, tam gdzie dotychczas rzeźbiliśmy ręcznie tego typu „dodatki”.