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”.