Strumienie w Javie 8

Odkrywam na poważnie Javę 8. Jestem tak zachwycona strumieniami, że aż się muszę podzielić!

Przykład (Java 8)

Zacznę od przykładu. Napisałam ostatnio taki kod:

Co robi ten fragment? Zwraca tablicę zawierającą te klucze z mapy, których wartości są większe od 0.

Dokładniej:

  1. Odwołuje się do obiektu mapy (map).
  2. Pobiera zbiór par klucz-wartość przechowywany w tej mapie (entrySet()).
  3. Zamienia ten zbiór na strumień, albo, poprawniej, odczytuje ten zbiór poprzez strumień (stream()).
  4. Filtruje strumień, pozostawiając w nim (a właściwie w nowym strumieniu) tylko te pary klucz-wartość, w których wartość jest większa od zera. Do filtrowania używana jest lambda, czyli definiowana w miejscu anonimowa funkcja zwracająca wartość typu boolean, przekazana jako parametr (.filter(entry -> entry.getValue() > 0)).
  5. Na wszystkich elementach (typu Map.Entry) pozostałych w strumieniu wykonywana jest funkcja zwracająca klucz (.map(entry -> entry.getKey())).
  6. Na samym końcu wywoływana jest metoda toArray z interfejsu Stream, do której przekazujemy generator (toArray(String[]::new)), czyli funkcję, dzięki której strumień wie, ile pamięci zaalokować. Istnieje bezargumentowa wersja tej metody, tyle że przy jej użyciu otrzymamy tablicę obiektów typu Object.

Przykład (Java 7 i starsze)

Po napisaniu tych paru linijek (ciągle mam problemy z formatowaniem takiego kodu i liczeniem linii) zaczęłam się zastanawiać, jak wyglądałby ten kod w wersji przedstrumieniowej. Musiałoby to być coś takiego:

Porównanie

Kilka różnic:

Java 7 Java 8
Definicja zadania Algorytm / opis procedury Strumień przetwarzania / deklaracja oczekiwanego wyniku
Kolejność przetwarzania Sekwencyjna Nieokreślona – może zostać poddana optymalizacji
Typy danych W niektórych miejscach, jak pętla for, trzeba je powtórzyć Kompilator jest w stanie je wywnioskować, nie trzeba ich powtarzać (np. w definicjach lambd)
Układ kodu Kod z blokami i wcięciami Łańcuch wywołań (oczywiście w przypadku bardziej złożonych lambd również trzeba stosować bloki)

Uczciwie dodam, że po stronie minusów stosowania strumieni muszę na razie dopisać debugowanie. W przypadku wystąpienia problemu, odrobinę trudniej jest mi się zorientować, co dokładnie poszło nie tak.

Czy naprawdę nie było innego wolnego słowa?

Strumieni z Javy 8 (z pakietu java.util.stream) nie należy mylić ze strumieniami służącymi do obsługi wejścia i wyjścia (z pakietu java.io). Co za geniusz wymyślił tę nazwę – nie wiem. Być może chodziło i uniknięcie jeszcze gorszego słowa „monada”.

Jeśli chcesz wyszukać w Google informacji na temat „nowych” strumieni, używaj nazwy „Java 8 Streams”.

Co się dzieje w tle?

Strumień nie przechowuje własnych danych – można uznać go za „widok” na dane pochodzące z kolekcji lub innego źródła. Podczas potokowego przetwarzania danych na przykład za pomocą kilku kolejnych operacji map, strumień nie jest modyfikowany, tylko zwracany jest wtedy nowy strumień, oferujący inne dane.

Wniosek

Wygodne. Używać.

Kiedy po raz pierwszy, w ramach prezentacji na Poznań JUG, zobaczyłam strumienie, nie byłam zachwycona. Pomyślałam sobie, że to kolejny wynalazek, który świetnie działa na wymyślonych, zabawkowych przykładach, a który okazuje się bezużyteczny w prawdziwym świecie. Nic bardziej mylnego, w pracy korzystam z nich teraz regularnie.

PS. Sklejanie Stringów

Od lat jeden z najbardziej irytujących braków w bibliotece standardowej Javy to sklejacz łańcuchów znaków. Teraz, jeśli chcesz za pomocą np. średników połączyć słowa przechowywane w kolekcji, nie już musisz pisać specjalnej funkcji ani załączać zewnętrznych bibliotek. Można tak:

Komentarze

4 myśli nt. „Strumienie w Javie 8”

  1. Hm, jestem skonfundowany. Co takiego mają strumienie (w rozumieniu Javy 8), czego nie mają zwykłe listy? W powyższym przykładzie robiłaś filter i map (do kompletu standardowych operacji „funkcyjnych” brakowało reduce) – ale to chyba można zrobić ze zwykłą listą? Czy może chodzi o „delayed (lazy) evaluation” (nie wiem, jak to się nazywa po polsku)?

    1. Tyle że właśnie zwykłe listy w Javie nie udostępniają tych operacji! 🙂 Domyślam się, że Twoje rozczarowanie wynika z przyzwyczajenia do funkcyjnego i opartego o listy świata Lispa.

      Co do braku reduce, tu akurat mamy collect, widoczne w ostatnim przykładzie.

      Lazy evaluation jest istotne, bo pozwala na wprowadzenie strumieni nieskończonych (także nowość w Javie!).

      Sprowadzenie strumienia do zaawansowanej listy to jednak spore uproszczenie, chociażby ze względu na pochodzenie danych, ale także sposób ich przetwarzania (mamy np. parallel streams).

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *