Naprawianie Javy 8 – krotki
Java 8 dała nam wiele wspaniałych rzeczy. Jednak nadal nie dostarczyła nam API krotek (ang. tuples). Z pomocą przychodzą oczywiście różne biblioteki. Chociażby JavaTuples, którą należy już nazywać Java Truples, bo nie była aktualizowana od dawien dawna. Choć ostatnio dorzucili… konfigurację OSGi. Inną biblioteką jest osławiony Javaslang. Zobaczmy co można z jej pomocą uzyskać.
Tworzenie i dostęp do elementów
By utworzyć nową krotkę, musimy skorzystać z jednej ze statycznych metod fabrykujących o wspólnej nazwie of z interfejsu Tuple. Różnią się one ilością parametrów:
Listing 1. Tworzenie pięcioelementowej krotki.
public void creatingAndAccess() {
Tuple5<Integer, Integer, Integer, String, String> wyliczanka = Tuple.of(1, 2, 3, "Baba Jaga", "patrzy");
}
Teraz by odwołać się, do któregoś z elementów możemy użyć zarówno pola, jak i metody:
Listing 2. Dostęp do elementów krotki.
public void creatingAndAccess() {
Tuple5<Integer, Integer, Integer, String, String> wyliczanka = Tuple.of(1, 2, 3, "Baba Jaga", "patrzy");
System.out.println(wyliczanka._1);
System.out.println(wyliczanka._2());
System.out.println(wyliczanka._1);
System.out.println(wyliczanka._4());
System.out.println(wyliczanka._5);
}
Jedynym ograniczeniem jest maksymalna wielkość krotki. W bibliotece Javaslang to 8. Z drugiej strony implementacja większych krotek jest prosta, a i trzeba bardziej uważać, co się projektuje, by nie musieć samodzielnie rzeźbić krotki o dwudziestu elementach.
Transformowanie
Jak już mamy naszą krotkę, to możemy ją zamienić na jakiś sensowny obiekt. W tym celu wykorzystamy metodę transform.
Listing 3. Przykład transformacji krotki
public void transforming() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
String message = alaIKot.transform((who, whom) -> who + " ma " + whom);
System.out.println(message);
}
W tym miejscu warto zauważyć, że Javaslang używa funkcji z API Javy 8 tam gdzie można. Zatem argumentem metody transform jest BiFunction, a nie Function2.
Wielkość i sekwencjonowanie
Gdy pracujemy z krotkami, to czasami potrzebujemy wiedzieć, jaka jest ich wielkość. W tym celu mamy metodę arity.
Listing 4. Wielkość krotki z arity
public void arityAndSeq() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
System.out.println(alaIKot.arity());
}
A gdy chcemy zamienić krotkę na sekwencję, reprezentowaną przez Seq, to możemy użyć toSeq:
Listing 5. Sekwencjonowanie z toSeq
public void arityAndSeq() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
Seq<?> elems = alaIKot.toSeq();
System.out.println(elems);
}
Sama sekwencja to dość ciekawa sprawa, ale o tym innym razem.
Mapowanie
Krotka to uporządkowany zbiór elementów różnych typów. W językach funkcyjnych takich jak erlang czy Elixir krotki stanowią podstawę przy tworzeniu bardziej skomplikowanych struktur danych. By w pełni wykorzystać ich możliwości, musimy mieć możliwość mapowania, czyli inaczej mówiąc transformacji krotki na inną o tej samej wielkości. W tym celu Tuple posiada zestaw metod map oraz mapX. Gdy chcemy mapować krotkę jako całość możemy użyć metody map, która jako parametr przyjmuje funkcję o liczbie parametrów równej wielkości krotki:
Listing 6. Mapowanie krotki jako całości
public void mapping() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
System.out.println(alaIKot.map((who, whom) -> Tuple.of(whom, who)));
}
Jest to przydatne, ale nie zawsze mamy do dyspozycji odpowiednią funkcję. Dlatego też istnieje map, które jako parametry przyjmuje funkcje o pojedynczym parametrze zgodnym z typem elementu na danej pozycji:
Listing 7. Mapowanie krotki po elemencie
public void mapping() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
System.out.println(alaIKot.map(String::length, String::length));
}
To rozwiązanie jest całkiem niezłe, bo zapewnia odpowiednią elastyczność. Znacznie łatwiej jest pisać proste funkcje o jednym argumencie niż kobyłki o na przykład 5 argumentach. Jednak nadal pozostają przypadki, gdy naszą funkcję chcemy zaaplikować tylko do jednego elementu, a pozostałe argumenty traktujemy funkcją tożsamościowąW. W takim przypadku bezsensowne jest wywoływanie metody map z poprzedniego przykładu. Javaslang udostępnia nam jednak grupę metod mapX, gdzie X to numer kolejnych elementów (nieludzko zrobiona, bo od jedynki). Gdy wywołamy taką metodę to otrzymujemy nową krotkę, w której tylko jeden element został zmieniony:
Listing 8. Mapowanie pojedynczego elementu krotki
public void mapping() {
Tuple2<String, String> alaIKot = Tuple.of("Ala", "Kot");
System.out.println(alaIKot.map1(String::length));
System.out.println(alaIKot.map2(String::length));
}
Oczywiście wywołania możemy łączyć:
Listing 9. Mapowanie pojedynczych elementów krotki
public void mapping() {
Tuple3<String, String, String> alaIKot = Tuple.of("Ala", "ma", "Kot");
System.out.println(alaIKot.map1(String::length).map3(String::length));
}
Przy czym, należy pamiętać, że każde wywołanie oznacza utworzenie nowego obiektu. W pewnych przypadkach może być to niepożądane zachowanie.
Podsumowanie
Krotki udostępnione w bibliotece Javaslang są bardzo fajnym dodatkiem do naszej skrzynki z narzędziami. Mają wszystko to, czego oczekujemy od tego typu struktury. Ciekawostką jest to, że kod źródłowy jest automatycznie generowany za pomocą generatora napisanego w Scali.