Strumienie w Javie

Strumienie są wygodnym narzędziem służącym do przetwarzania danych. Jest to bardzo szerokie zagadnienie gdyż dane mogą pochodzić z różnych źródeł. Sposób działania interfejsu Stream znacząco upraszcza pewne działania na zbiorach i może korzystnie wpłynąć na optymalizację działania programów. Strumienie są nowością wprowadzoną do Javy w wersji 8.

Czym są strumienie?

Strumienie należy rozumieć jako abstrakcję, która pozwala uprościć przetwarzanie danych. Nie należy ich mylić z kolekcjami, gdyż nie posiadają możliwości przechowywania ani zapisywania danych. Za każdym razem, gdy skończymy przetwarzanie informacji musimy skorzystać np. Z któregoś interfejsu kolekcji.

Ważną cechą strumieni jest brak ingerencji w źródło. W związku z tym metody, które np. filtrują elementy po jakiejś cesze pomniejszając wyjściowy zbiór, tworzą nowy strumień.

Kolejną cechą strumieni jest brak ograniczeń jeśli chodzi o rozmiar. Nie muszą posiadać ograniczenia rozmiaru jak np. kolekcje, tablice. Strumień można wykorzystać tylko raz. Każdy następny dostęp do danych wymaga utworzenia nowego.

Strumienie są przykładem przetwarzania leniwego. Dzięki temu istnieje możliwość optymalizowania wykonywanych operacji.

Przykład wykorzystania strumienia

Aby zobrazować zarówno sposób działania strumieni jak i ich zalety posłużymy się przykładem. Wykonamy zatem proste zadanie, którego treść jest następująca. Z podanej lokalizacji wypiszemy wszystkie pliki w formacie pdf znajdujące się też w podfolderach.

W tym celu skorzystamy z klasy Files, która posiada metodę walk(). Zwraca ona strumień zawierający co najmniej jeden element – podany w argumencie folder (zakładamy, że nie wystąpił wyjątek i podana lokalizacja jest poprawna). Jeśli folder będzie zawierał wewnętrzne katalogi oraz pliki metoda walk() doda je do zwracanego strumienia.

Załóżmy, że posiadamy poniższe drzewko folderów.

Drzewo folderów

Drzewo folderów

Poniżej znajduje się kod wykonujący powyższe zadanie.

Wynikiem uruchomienia powyższego kodu jest.

D:\Folder\dokuemnt.pdf
D:\Folder\Folder1\Folder1_1\dokument1_1.pdf

W pierwszym kroku utworzyliśmy strumień zawierający wszystkie możliwe ścieżki wewnątrz podanej lokalizacji. Następnie tworzymy kolejny strumień, w którym metodą filter() zawężamy elementy do tych, które mają rozszerzenie pdf. Na koniec metodą collect() Zapisujemy powstały strumień do listy.

W powyższym kodzie użyliśmy wyrażeń lambda.

Tworzenie strumieni

Pierwszym krokiem w pracy ze strumieniami jest zawsze jego utworzenie. W naszym przykładzie zrobiliśmy to przy pomocy metody walk() z klasy Files. Natomiast strumienie można utworzyć również z innych źródeł. Zacznijmy od najprostszej postaci czyli pustego strumienia.

Strumienie utworzone z podanych wartości

Kolekcja przerobiona na strumień

Strumienie z tablic wymagają użycia klasy Arrays. W przypadku typów prostych musimy użyć specjalnego interfejsu np. IntStream.

Modyfikowanie strumieni

W naszym przykładzie wypisującym użyliśmy takich metod jak filter() oraz map(). Obie służą do przekształcania strumieni wyjściowych.

Metoda filter() jest metodą ograniczającą strumień. Podajemy w niej warunki, które muszą zostać spełnione, aby dodać element do nowego filtra. W naszym przykładzie użyliśmy go w celu ograniczenia wyników tylko do plików w formacie pdf. Warunek możemy postawić dowolny. Innym przykładem może być relacja mniejszy większy lub jakikolwiek warunek logiczny.

Metoda map() pozwala na zamianę jednego typu na inny. W naszym przykładzie zamieniliśmy strumień obiektów typu Path na String.

Generowanie wyników

Metoda collect() pozwala na zapisanie strumienia do określonej kolekcji. W naszym przypadku użyliśmy parametru Collectors::toList(). Istnieje również możliwość użycia opcji Collectors::toMap().

Podobnie możemy wynik zapisać do tablic używając metody toArray(). Przykładem użycia może być następujący strumień.

W przypadku strumieni oraz kolekcji możemy użyć metody foreach().