JavaRush /Blog Java /Random-PL /Przerwa kawowa #177. Szczegółowy przewodnik po strumieniu...

Przerwa kawowa #177. Szczegółowy przewodnik po strumieniu Java w Javie 8

Opublikowano w grupie Random-PL
Źródło: Hackernoon Ten post zawiera szczegółowy samouczek na temat pracy z Java Stream wraz z przykładami kodu i objaśnieniami. Przerwa kawowa #177.  Szczegółowy przewodnik po strumieniu Java w Javie 8 - 1

Wprowadzenie do wątków Java w Javie 8

Strumienie Java, wprowadzone jako część Java 8, służą do pracy ze zbiorami danych. Same w sobie nie są strukturą danych, ale można ich używać do wprowadzania informacji z innych struktur danych poprzez porządkowanie i potokowanie w celu uzyskania końcowego wyniku. Uwaga: Ważne jest, aby nie mylić Strumienia i Wątku, ponieważ w języku rosyjskim oba terminy są często określane w tym samym tłumaczeniu „przepływ”. Stream oznacza obiekt służący do wykonywania operacji (najczęściej przesyłania lub przechowywania danych), natomiast Thread (dosłowne tłumaczenie - wątek) oznacza obiekt, który umożliwia wykonanie określonego kodu programu równolegle z innymi gałęziami kodu. Ponieważ Stream nie jest osobną strukturą danych, nigdy nie zmienia źródła danych. Strumienie Java mają następujące funkcje:
  1. Java Stream może być używany przy użyciu pakietu „java.util.stream”. Można go zaimportować do skryptu za pomocą kodu:

    import java.util.stream.* ;

    Korzystając z tego kodu, możemy również łatwo zaimplementować kilka wbudowanych funkcji w Java Stream.

  2. Java Stream może akceptować dane wejściowe ze zbiorów danych, takich jak kolekcje i tablice w Javie.

  3. Java Stream nie wymaga zmiany struktury danych wejściowych.

  4. Java Stream nie zmienia źródła. Zamiast tego generuje dane wyjściowe przy użyciu odpowiednich metod potokowych.

  5. Strumienie Java podlegają operacjom pośrednim i terminalowym, które omówimy w kolejnych sekcjach.

  6. W Java Stream operacje pośrednie są wykonywane potokowo i mają formę leniwej oceny. Kończą się funkcjami terminalowymi. Stanowi to podstawowy format korzystania ze strumienia Java.

W następnej sekcji przyjrzymy się różnym metodom używanym w Javie 8 do tworzenia strumienia Java w razie potrzeby.

Tworzenie strumienia Java w Javie 8

Wątki Java można tworzyć na kilka sposobów:

1. Utworzenie pustego strumienia przy pomocy metody Stream.empty().

Możesz utworzyć pusty strumień do późniejszego użycia w kodzie. Jeśli użyjesz metody Stream.empty() , zostanie wygenerowany pusty strumień, nie zawierający żadnych wartości. Ten pusty strumień może się przydać, jeśli chcemy pominąć wyjątek wskaźnika zerowego w czasie wykonywania. Aby to zrobić, możesz użyć następującego polecenia:
Stream<String> str = Stream.empty();
Powyższa instrukcja wygeneruje pusty strumień o nazwie str, bez żadnych elementów w nim zawartych. Aby to sprawdzić, po prostu sprawdź liczbę lub rozmiar strumienia, używając terminu str.count() . Na przykład,
System.out.println(str.count());
W rezultacie na wyjściu otrzymujemy 0 .

2. Utwórz strumień za pomocą metody Stream.builder() z instancją Stream.Builder

Możemy również użyć Konstruktora strumieni , aby utworzyć strumień przy użyciu wzorca projektowego konstruktora. Przeznaczony jest do etapowej budowy obiektów. Zobaczmy, jak możemy utworzyć instancję strumienia za pomocą Kreatora strumieni .
Stream.Builder<Integer> numBuilder = Stream.builder();

numBuilder.add(1).add(2).add( 3);

Stream<Integer> numStream = numBuilder.build();
Używając tego kodu, możesz utworzyć strumień o nazwie numStream zawierający elementy int . Całość odbywa się dość szybko dzięki utworzonej jako pierwszej instancji Stream.Builder o nazwie numBuilder .

3. Utwórz strumień o określonych wartościach za pomocą metody Stream.of().

Innym sposobem utworzenia strumienia jest użycie metody Stream.of() . Jest to prosty sposób na utworzenie strumienia o zadanych wartościach. Deklaruje, a także inicjuje wątek. Przykład wykorzystania metody Stream.of() do utworzenia strumienia:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Ten kod utworzy strumień zawierający elementy int , tak jak to zrobiliśmy w poprzedniej metodzie przy użyciu Stream.Builder . Tutaj bezpośrednio utworzyliśmy strumień za pomocą Stream.of() z predefiniowanymi wartościami [1, 2 i 3] .

4. Utwórz strumień z istniejącej tablicy za pomocą metody Arrays.stream().

Inną popularną metodą tworzenia wątku jest użycie tablic w Javie. Strumień w tym przypadku jest tworzony z istniejącej tablicy przy użyciu metody Arrays.stream() . Wszystkie elementy tablicy są konwertowane na elementy strumieniowe. Oto dobry przykład:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Ten kod wygeneruje numStream zawierający zawartość tablicy o nazwie arr, która jest tablicą liczb całkowitych.

5. Łączenie dwóch istniejących strumieni przy pomocy metody Stream.concat().

Inną metodą, którą można wykorzystać do utworzenia strumienia, jest metoda Stream.concat() . Służy do łączenia dwóch wątków w jeden wątek. Obydwa strumienie są łączone w odpowiedniej kolejności. Oznacza to, że pierwszy wątek pojawia się jako pierwszy, po nim następuje drugi wątek i tak dalej. Przykład takiej konkatenacji wygląda następująco:
Stream<Integer> numStream1 = Stream.of(1, 2, 3, 4, 5);

Stream<Integer> numStream2 = Stream.of(1, 2, 3);

Stream<Integer> combinedStream = Stream.concat( numStream1, numStream2);
Powyższa instrukcja utworzy końcowy strumień o nazwie CombinedStream zawierający elementy pierwszego strumienia numStream1 i drugiego strumienia numStream2, jeden po drugim .

Rodzaje operacji na Java Stream

Jak już wspomniano, w Javie 8 można wykonywać dwa rodzaje operacji na strumieniu Java: pośrednie i końcowe. Przyjrzyjmy się każdemu z nich bardziej szczegółowo.

Operacje pośrednie

Operacje pośrednie generują strumień wyjściowy i są wykonywane tylko w przypadku napotkania operacji terminalowej. Oznacza to, że operacje pośrednie są wykonywane leniwie, potokowo i można je zakończyć jedynie operacją terminalową. O leniwej ocenie i potoku dowiesz się nieco później. Przykładami operacji pośrednich są następujące metody: filter() , map() , Different() , peek() , sorted() i kilka innych.

Operacje terminalowe

Operacje terminalowe kończą wykonywanie operacji pośrednich, a także zwracają końcowe wyniki strumienia wyjściowego. Ponieważ operacje terminalowe sygnalizują koniec leniwego wykonywania i potokowania, tego wątku nie można ponownie użyć po przejściu operacji terminalowej. Przykładami operacji terminalowych są następujące metody: forEach() , zbieraj() , count() , redukuj() i tak dalej.

Przykłady operacji z Java Stream

Operacje pośrednie

Oto kilka przykładów niektórych operacji pośrednich, które można zastosować do strumienia Java:

filtr()

Ta metoda służy do filtrowania elementów strumienia pasujących do określonego predykatu w Javie. Te odfiltrowane elementy tworzą następnie nowy strumień. Rzućmy okiem na przykład, aby lepiej zrozumieć.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> even = numStream.filter(n -> n % 2 == 0) .collect(Collectors.toList()); System.out.println(even);
Wniosek:
[98]
Objaśnienie: W tym przykładzie widać, że elementy parzyste (podzielne przez 2) są filtrowane przy użyciu metody filter() i zapisywane na liście liczb całkowitych numStream , której zawartość jest później drukowana. Ponieważ 98 jest jedyną parzystą liczbą całkowitą w strumieniu, jest ona wydawana na wyjściu.

mapa()

Ta metoda służy do tworzenia nowego strumienia poprzez wykonanie odwzorowanych funkcji na elementach oryginalnego strumienia wejściowego. Być może nowy strumień ma inny typ danych. Przykład wygląda następująco:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> d = numStream.map(n -> n*2) .collect(Collectors.toList()); System.out.println(d);
Wniosek:
[86, 130, 2, 196, 126]
Objaśnienie: Tutaj widzimy, że metoda map() służy do prostego podwojenia każdego elementu strumienia numStream . Jak widać na wynikach, każdy z elementów strumienia został pomyślnie podwojony.

odrębny()

Ta metoda służy do pobierania tylko pojedynczych elementów w strumieniu poprzez odfiltrowanie duplikatów. Przykład tego samego wygląda następująco:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Wniosek:
[43, 65, 1, 98, 63]
Objaśnienie: W tym przypadku dla numStream używana jest metoda Different() . Pobiera wszystkie pojedyncze elementy z numList, usuwając duplikaty ze strumienia. Jak widać na wyjściu, nie ma duplikatów, w przeciwieństwie do strumienia wejściowego, który początkowo miał dwa duplikaty (63 i 1).

zerkać()

Ta metoda służy do śledzenia pośrednich zmian przed wykonaniem operacji terminalowej. Oznacza to, że za pomocą funkcji peek() można wykonać operację na każdym elemencie strumienia w celu utworzenia strumienia, na którym można wykonać dalsze operacje pośrednie.
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> nList = numStream.map(n -> n*10) .peek(n->System.out.println("Mapped: "+ n)) .collect(Collectors.toList()); System.out.println(nList);
Wniosek:
Zmapowane: 430 Zmapowane: 650 Zmapowane: 10 Zmapowane: 980 Zmapowane: 630 [430, 650, 10, 980, 630]
Objaśnienie: W tym przypadku metoda peek() jest używana do generowania wyników pośrednich, podczas gdy metoda map() jest stosowana do elementów strumienia. Tutaj możemy zauważyć, że jeszcze przed użyciem operacji terminalowejcollect() do wydrukowania końcowej zawartości listy w instrukcji print , wynik dla każdego odwzorowania elementu strumienia jest drukowany z góry sekwencyjnie.

posortowane()

Metoda sorted() służy do sortowania elementów strumienia. Domyślnie sortuje elementy w kolejności rosnącej. Jako parametr możesz także określić konkretny porządek sortowania. Przykładowa implementacja tej metody wygląda następująco:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Wniosek:
1 43 ​​​​63 65 98
Objaśnienie: W tym przypadku metoda sorted() jest domyślnie używana do sortowania elementów strumienia w kolejności rosnącej (ponieważ nie określono żadnej konkretnej kolejności). Widać, że pozycje wydrukowane na wyjściu są ułożone w kolejności rosnącej.

Operacje terminalowe

Oto kilka przykładów niektórych operacji terminalowych, które można zastosować do strumieni Java:

dla każdego()

Metoda forEach() służy do iteracji po wszystkich elementach strumienia i wykonywania funkcji po kolei na każdym elemencie. Działa to jako alternatywa dla instrukcji pętli, takich jak for , while i inne. Przykład:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Wniosek:
43 65 1 98 63
Objaśnienie: W tym przypadku metoda forEach() służy do drukowania każdego elementu strumienia jeden po drugim.

liczyć()

Metoda count() służy do pobierania całkowitej liczby elementów znajdujących się w strumieniu. Jest ona podobna do metody size() , która jest często używana do określenia całkowitej liczby elementów w kolekcji. Przykład użycia metody count() ze strumieniem Java jest następujący:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Wniosek:
5
Objaśnienie: Ponieważ numStream zawiera 5 elementów całkowitych, użycie na nim metody count() spowoduje wyświetlenie 5.

zbierać()

Metoda Collect() służy do wykonywania modyfikowalnych redukcji elementów strumienia. Można go użyć do usunięcia treści ze strumienia po zakończeniu przetwarzania. Do wykonywania redukcji wykorzystuje klasę Collector .
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); List<Integer> odd = numStream.filter(n -> n % 2 == 1) .collect(Collectors.toList()); System.out.println(odd);
Wniosek:
[43, 65, 1, 63]
Objaśnienie: W tym przykładzie wszystkie nieparzyste elementy w strumieniu są filtrowane i gromadzone/redukowane do listy o nazwie odd . Na koniec drukowana jest lista nieparzystych.

min() i max()

Metodę min() jak sama nazwa wskazuje, można zastosować w strumieniu w celu znalezienia w nim minimalnego elementu. Podobnie metody max() można użyć do znalezienia maksymalnego elementu w strumieniu. Spróbujmy zrozumieć, jak można je wykorzystać na przykładzie:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); int smallest = numStream.min((m, n) -> Integer.compare(m, n)).get(); System.out.println("Smallest element: " + smallest);
numStream = Stream.of(43, 65, 1, 98, 63); int largest = numStream.max((m, n) -> Integer.compare(m, n)).get(); System.out.println("Largest element: " + largest);
Wniosek:
Najmniejszy element: 1 Największy element: 98
Objaśnienie: W tym przykładzie wydrukowaliśmy najmniejszy element strumienia numStream za pomocą metody min() , a największy element za pomocą metody max() . Zauważ, że tutaj, przed użyciem metody max() , ponownie dodaliśmy elementy do numStream . Dzieje się tak, ponieważ min() jest operacją terminalową i niszczy zawartość pierwotnego strumienia, zwracając jedynie wynik końcowy (który w tym przypadku był „najmniejszą” liczbą całkowitą).

findAny() i findFirst()

findAny() zwraca dowolny element strumienia jako Opcjonalny . Jeśli strumień jest pusty, zwróci również wartość opcjonalną , która będzie pusta. findFirst() zwraca pierwszy element strumienia jako Opcjonalny . Podobnie jak metoda findAny() metoda findFirst() również zwraca pusty parametr opcjonalny , jeśli odpowiedni strumień jest pusty. Przyjrzyjmy się poniższemu przykładowi opartemu na tych metodach:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); Optional<Integer> opt = numStream.findFirst();System.out.println(opt); numStream = Stream.empty(); opt = numStream.findAny();System.out.println(opt);
Wniosek:
Opcjonalne[43] Opcjonalne.puste
Objaśnienie: W pierwszym przypadku metoda findFirst() zwraca pierwszy element strumienia jako Opcjonalny . Następnie, gdy wątek zostanie ponownie przypisany jako pusty, metoda findAny() zwróci pustą opcję Opcjonalnie .

allMatch() , anyMatch() i noneMatch()

Metoda allMatch() służy do sprawdzania, czy wszystkie elementy w strumieniu pasują do określonego predykatu i zwraca wartość logiczną true , jeśli tak jest, w przeciwnym razie zwraca false . Jeśli strumień jest pusty, zwraca wartość true . Metoda anyMatch() służy do sprawdzania, czy którykolwiek z elementów strumienia pasuje do określonego predykatu. Zwraca wartość true , jeśli tak, w przeciwnym razie false . Jeśli strumień jest pusty, zwraca false . Metoda noneMatch() zwraca wartość true , jeśli żaden element w strumieniu nie pasuje do predykatu, lub wartość false w przeciwnym razie. Przykład ilustrujący to wygląda następująco:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); boolean flag = numStream.allMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.anyMatch(n -> n1); System.out.println(flag); numStream = Stream.of(43, 65, 1, 98, 63); flag = numStream.noneMatch(n -> n==1);System.out.println(flag);
Wniosek:
fałsz prawda fałsz
Objaśnienie: W przypadku strumienia numStream zawierającego jako element wartość 1, metoda allMatch() zwraca wartość false , ponieważ wszystkie elementy nie mają wartości 1, ale tylko jeden z nich. Metoda anyMatch() zwraca wartość true , ponieważ co najmniej jeden z elementów ma wartość 1. Metoda noneMatch() zwraca wartość false , ponieważ wartość 1 faktycznie istnieje jako element w tym strumieniu.

Leniwe oceny w strumieniu Java

Leniwa ocena prowadzi do optymalizacji podczas pracy ze strumieniami Java w Javie 8. Polegają one głównie na opóźnianiu operacji pośrednich do momentu napotkania operacji terminalowej. Leniwa ocena ma na celu zapobieganie niepotrzebnemu marnowaniu zasobów na obliczenia, dopóki wynik nie będzie rzeczywiście potrzebny. Strumień wyjściowy powstały w wyniku operacji pośrednich generowany jest dopiero po zakończeniu operacji terminalowej. Leniwa ocena działa ze wszystkimi operacjami pośrednimi w strumieniach Java. Bardzo przydatne zastosowanie leniwej oceny ma miejsce podczas pracy z nieskończonymi strumieniami. W ten sposób zapobiega się wielu niepotrzebnym przetwarzaniom.

Potoki w strumieniu Java

Potok w strumieniu Java składa się ze strumienia wejściowego, zera lub większej liczby operacji pośrednich ułożonych jedna za drugą i na koniec operacji terminalowej. Operacje pośrednie w strumieniach Java są wykonywane leniwie. To sprawia, że ​​pośrednie operacje potokowe są nieuniknione. W przypadku potoków, które są w zasadzie operacjami pośrednimi połączonymi w odpowiedniej kolejności, możliwe staje się leniwe wykonanie. Potoki pomagają śledzić operacje pośrednie, które należy wykonać po ostatecznym napotkaniu operacji terminalowej.

Wniosek

Podsumujmy teraz to, czego się dzisiaj nauczyliśmy. W tym artykule:
  1. Przyjrzeliśmy się szybko, czym są strumienie Java.
  2. Następnie nauczyliśmy się wielu różnych technik tworzenia wątków Java w Javie 8.
  3. Poznaliśmy dwa główne typy operacji (operacje pośrednie i operacje terminalowe), które można wykonać na strumieniach Java.
  4. Następnie szczegółowo przyjrzeliśmy się kilku przykładom operacji pośrednich i końcowych.
  5. Skończyło się na tym, że dowiedzieliśmy się więcej o leniwej ewaluacji i wreszcie o potokach w wątkach Java.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION