Archiwa tagu: streams

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: