JavaRush /Java Blog /Random-TL /Coffee break #177. Isang Detalyadong Gabay sa Java Stream...

Coffee break #177. Isang Detalyadong Gabay sa Java Stream sa Java 8

Nai-publish sa grupo
Pinagmulan: Hackernoon Ang post na ito ay nagbibigay ng isang detalyadong tutorial sa pagtatrabaho sa Java Stream kasama ang mga halimbawa ng code at mga paliwanag. Кофе-брейк #177. Подробное руководство по Java Stream в Java 8 - 1

Panimula sa Java Threads sa Java 8

Ang Java Streams, na ipinakilala bilang bahagi ng Java 8, ay ginagamit upang gumana sa mga koleksyon ng data. Ang mga ito ay hindi isang istraktura ng data sa kanilang sarili, ngunit maaaring magamit upang mag-input ng impormasyon mula sa iba pang mga istruktura ng data sa pamamagitan ng pag-order at pipelining upang makagawa ng isang pangwakas na resulta. Tandaan: Mahalagang huwag malito ang Stream at Thread, dahil sa Russian ang parehong mga termino ay madalas na tinutukoy sa parehong pagsasalin na "daloy". Ang stream ay nagsasaad ng isang bagay para sa pagsasagawa ng mga operasyon (pinakadalasang paglilipat o pag-iimbak ng data), habang ang Thread (literal na pagsasalin - thread) ay tumutukoy sa isang bagay na nagpapahintulot sa ilang partikular na program code na maisakatuparan sa iba pang mga sangay ng code. Dahil ang Stream ay hindi isang hiwalay na istraktura ng data, hindi nito kailanman binabago ang pinagmulan ng data. Ang mga stream ng Java ay may mga sumusunod na tampok:
  1. Maaaring gamitin ang Java Stream gamit ang package na “java.util.stream”. Maaari itong ma-import sa isang script gamit ang code:

    import java.util.stream.* ;

    Gamit ang code na ito, madali rin nating maipapatupad ang ilang built-in na function sa Java Stream.

  2. Maaaring tumanggap ang Java Stream ng input mula sa mga koleksyon ng data tulad ng mga koleksyon at array sa Java.

  3. Ang Java Stream ay hindi nangangailangan ng pagbabago sa istraktura ng data ng input.

  4. Hindi binabago ng Java Stream ang pinagmulan. Sa halip, bumubuo ito ng output gamit ang naaangkop na mga pamamaraan ng pipeline.

  5. Ang mga Java Stream ay napapailalim sa intermediate at terminal operations, na tatalakayin natin sa mga sumusunod na seksyon.

  6. Sa Java Stream, ang mga intermediate na operasyon ay pipeline at nangyayari sa isang tamad na format ng pagsusuri. Nagtatapos sila sa mga pag-andar ng terminal. Ito ang bumubuo sa pangunahing format para sa paggamit ng Java Stream.

Sa susunod na seksyon, titingnan natin ang iba't ibang mga pamamaraan na ginamit sa Java 8 upang lumikha ng isang Java Stream kung kinakailangan.

Paglikha ng Java Stream sa Java 8

Maaaring malikha ang mga thread ng Java sa maraming paraan:

1. Paglikha ng walang laman na stream gamit ang Stream.empty() na paraan

Maaari kang lumikha ng isang walang laman na stream para magamit sa ibang pagkakataon sa iyong code. Kung gagamitin mo ang Stream.empty() method , bubuo ng walang laman na stream, na walang mga value. Ang walang laman na stream na ito ay maaaring magamit kung gusto naming laktawan ang isang null pointer exception sa runtime. Upang gawin ito maaari mong gamitin ang sumusunod na command:
Stream<String> str = Stream.empty();
Ang pahayag sa itaas ay bubuo ng isang walang laman na stream na pinangalanang str na walang anumang elemento sa loob nito. Upang i-verify ito, tingnan lamang ang numero o laki ng stream gamit ang terminong str.count() . Halimbawa,
System.out.println(str.count());
Bilang resulta, nakakakuha tayo ng 0 sa output .

2. Creation потока с использованием метода Stream.builder() с экземпляром Stream.Builder

Мы также можем использовать Stream Builder для создания потока с помощью шаблона проектирования builder. Он предназначен для пошагового построения an objectов. Давайте посмотрим, How мы можем создать экземпляр потока с помощью Stream Builder.
Stream.Builder<Integer> numBuilder = Stream.builder();

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

Stream<Integer> numStream = numBuilder.build();
Используя этот code, можно создать поток с именем numStream, содержащий элементы int. Все выполняется достаточно быстро благодаря экземпляру Stream.Builder под именем numBuilder, который создается первым.

3. Creation потока с указанными значениями с помощью метода Stream.of()

Еще один способ создания потока предполагает использование метода Stream.of(). Это простой способ создания потока с заданными значениями. Он объявляет, а также инициализирует поток. Пример использования метода Stream.of() для создания потока:
Stream<Integer> numStream = Stream.of(1, 2, 3);
Этот code создаст поток, содержащий элементы int, How мы уже сделали в предыдущем методе с использованием Stream.Builder. Здесь мы напрямую создали поток, используя Stream.of() с заранее заданными значениями [1, 2 и 3].

4. Creation потока из существующего массива с использованием метода Arrays.stream()

Другой распространенный метод создания потока предполагает использование массивов в Java. Поток здесь создается из существующего массива с помощью метода Arrays.stream(). Все элементы массива преобразуются в элементы потока. Вот наглядный пример:
Integer[] arr = {1, 2, 3, 4, 5};

Stream<Integer> numStream = Arrays.stream(arr);
Этот code будет генерировать поток numStream, содержащий содержимое массива с именем arr, который представляет собой целочисленный массив (integer array).

5. An association двух существующих потоков с помощью метода Stream.concat()

Еще один метод, который можно использовать для создания потока, — это метод Stream.concat(). Он используется для объединения двух потоков с целью создания единого потока. Оба потока объединяются по порядку. Это означает, что первым идет первый поток, за которым следует второй поток, и так далее. Пример такой конкатенации выглядит следующим образом:
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);
Приведенный выше оператор создаст окончательный поток с именем combinedStream, содержащий один за другим элементы первого потока numStream1 и второго потока numStream2.

Типы операций с Java Stream

Как уже упоминалось, с Java Stream в Java 8 можно выполнять два типа операций: промежуточные (intermediate) и терминальные (terminal). Рассмотрим каждую из них более подробно.

Промежуточные операции

Промежуточные операции генерируют выходной поток (output stream) и выполняются только при встрече с терминальной операцией. Это означает, что промежуточные операции выполняются “лениво”, конвейерно и могут быть завершены только терминальной операцией. О ленивом вычислении и конвейерной обработке вы узнаете чуть позже. Примерами промежуточных операций являются следующие методы: filter(), map(), different(), peek(), sorted() и некоторые другие.

Терминальные операции

Терминальные операции завершают выполнение промежуточных операций, а также возвращают окончательные результаты выходного потока. Поскольку терминальные операции сигнализируют о завершении ленивого выполнения и конвейерной обработки, этот поток нельзя использовать снова после того, How он подвергся терминальной операции. Примерами терминальных операций являются следующие методы: forEach(), collect(), count(), reduce() и так далее.

Примеры операций с Java Stream

Промежуточные операции

Вот несколько примеров некоторых промежуточных операций, которые можно применять к Java Stream:

filter()

Этот метод используется для фильтрации элементов из потока, которые соответствуют определенному предикату в Java. Затем эти отфильтрованные элементы составляют новый поток. Давайте взглянем на пример, чтобы лучше понять лучше.
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);
Вывод:
[98]
Объяснение: В этом примере видно, что четные элементы (делящиеся на 2) фильтруются с помощью метода filter() и сохраняются в целочисленном списке numStream, содержимое которого печатается позже. Поскольку 98 — единственное четное целое число в потоке, оно печатается в выводе.

map()

Этот метод используется для создания нового потока путем выполнения сопоставленных функций над elementми исходного входного потока. Возможно, новый поток имеет другой тип данных. Пример выглядит следующим образом:
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);
Вывод:
[86, 130, 2, 196, 126]
Объяснение: Здесь мы видим, что метод map() используется для простого удвоения каждого element потока numStream. Как видно из вывода, каждый из элементов в потоке успешно удвоен.

distinct()

Этот метод используется для извлечения только отдельных элементов в потоке путем фильтрации дубликатов. Пример того же выглядит следующим образом:
Stream<Integer> numStream = Stream.of(43,65,1,98,63,63,1); List<Integer> numList = numStream.distinct() .collect(Collectors.toList()); System.out.println(numList);
Вывод:
[43, 65, 1, 98, 63]
Объяснение: В этом случае для numStream используется метод different(). Он извлекает все отдельные элементы в списке numList путем удаления дубликатов из потока. Как видно из выводных данных, дубликатов нет, в отличие от входного потока, который изначально имел два дубликата (63 и 1).

peek()

Этот метод используется для отслеживания промежуточных изменений перед выполнением терминальной операции. Это означает, что peek() можно использовать для выполнения операции над каждым элементом потока для создания потока, над которым могут выполняться дальнейшие промежуточные операции.
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);
Вывод:
Mapped: 430 Mapped: 650 Mapped: 10 Mapped: 980 Mapped: 630 [430, 650, 10, 980, 630]
Объяснение: Здесь метод peek() используется для генерации промежуточных результатов, поскольку метод map() применяется к elementм потока. Здесь мы можем заметить, что даже до применения терминальной операции collect() для печати окончательного содержимого списка в операторе print результат для каждого сопоставления element потока печатается последовательно заранее.

sorted()

Метод sorted() используется для сортировки элементов потока. По умолчанию он сортирует элементы в порядке возрастания. Также можно указать конкретный порядок сортировки в качестве параметра. Пример реализации этого метода выглядит следующим образом:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.sorted().forEach(n -> System.out.println(n));
Вывод:
1 43 ​​63 65 98
Объяснение: Здесь метод sorted() используется для сортировки элементов потока в порядке возрастания по умолчанию (поскольку конкретный порядок не указан). Можно увидеть, что элементы, напечатанные в выводе, упорядочены в возрастающем порядке.

Терминальные операции

Вот несколько примеров некоторых терминальных операций, которые можно применять к потокам Java:

forEach()

Метод forEach() используется для перебора всех элементов потока и выполнения функции для каждого element один за другим. Это действует How альтернатива операторам цикла, таким How for, while и другим. Пример:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); numStream.forEach(n -> System.out.println(n));
Вывод:
43 65 1 98 63
Объяснение: Здесь метод forEach() используется для печати каждого element потока один за другим.

count()

Метод count() используется для извлечения общего количества элементов, присутствующих в потоке. Он похож на метод size(), который часто используется для определения общего количества элементов в коллекции. Пример использования метода count() с Java Stream выглядит следующим образом:
Stream<Integer> numStream = Stream.of(43, 65, 1, 98, 63); System.out.println(numStream.count());
Вывод:
5
Объяснение: Поскольку поток numStream содержит 5 целочисленных элементов, то при использовании для него метода count() вывод будет 5.

collect()

Метод collect() используется для выполнения изменяемых сокращений элементов потока. Его можно использовать для удаления содержимого из потока после завершения обработки. Для проведения редукций он использует класс 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);
Вывод:
[43, 65, 1, 63]
Объяснение: В этом примере все нечетные элементы в потоке фильтруются и собираются/сокращаются в список с именем odd (нечетные). В конце печатается список нечетных.

min() и max()

Метод min(), How следует из названия, может использоваться в потоке для поиска в нем минимального element. Точно так же метод max() можно использовать для поиска максимального element в потоке. Давайте попробуем понять, How их можно использовать, на примере:
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);
Вывод:
Smallest element: 1 Largest element: 98
Объяснение: В этом примере мы напечатали наименьший элемент в потоке numStream с помощью метода min() и самый большой элемент с помощью метода max(). Обратите внимание, что здесь перед применением метода max() мы снова добавor элементы в поток numStream. Это связано с тем, что min() является терминальной операцией и уничтожает содержимое исходного потока, возвращая только окончательный результат (который в данном случае был целочисленным “smallest”).

findAny() и findFirst()

findAny() возвращает любой элемент потока How Optional. Если поток пуст (empty), он вернет также meaning Optional, которое будет пустым. findFirst() возвращает первый элемент потока How Optional. Как и в случае с методом findAny(), метод findFirst() также возвращает пустой параметр Optional, если соответствующий поток пуст. Давайте взглянем на следующий пример, основанный на этих методах:
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);
Вывод:
Optional[43] Optional.empty
Объяснение: Здесь, в первом случае, метод findFirst() возвращает первый элемент потока How Optional. Затем, когда поток переназначается How пустой поток, метод findAny() возвращает пустой Optional.

allMatch(), anyMatch() и noneMatch()

Метод allMatch() используется для проверки того, соответствуют ли все элементы в потоке определенному предикату, и возвращает логическое meaning true, если это так, в противном случае возвращается false. Если поток пуст, он возвращает true. Метод anyMatch() используется для проверки того, соответствует ли Howой-либо из элементов в потоке определенному предикату. Он возвращает true, если это так, и false в противном случае. Если поток пуст, он возвращает false. Метод noneMatch() возвращает true, если ни один элемент потока не соответствует предикату, и false в противном случае. Пример, иллюстрирующий это, выглядит следующим образом:
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);
Вывод:
false true false
Объяснение: Для потока numStream, содержащего 1 в качестве element, метод allMatch() возвращает false, так How все элементы не равны 1, а только один из них. Метод anyMatch() возвращает true, поскольку хотя бы один из элементов equals 1. Метод noneMatch() возвращает false, поскольку 1 действительно существует How элемент в этом потоке.

Lazy Evaluations (ленивые вычисления) в Java Stream

Ленивые вычисления приводят к оптимизации при работе с Java Streams в Java 8. Они в основном связаны с задержкой выполнения промежуточных операций до тех пор, пока не встретится терминальная операция. Ленивые вычисления отвечают за предотвращение ненужной траты ресурсов на вычисления до тех пор, пока результат действительно не понадобится. Выходной поток, полученный в результате промежуточных операций, генерируется только после выполнения терминальной операции. Ленивые вычисления работают со всеми промежуточными операциями в потоках Java. Очень полезное применение ленивых вычислений проявляется при работе с бесконечными потоками (infinite streams). В этом случае предотвращается много ненужной обработки.

Конвейеры в Java Stream

Конвейер (pipeline) в Java Stream включает в себя входной поток, ноль or несколько промежуточных операций, выстроенных одна за другой, и, наконец, терминальную операцию. Промежуточные операции в Java Streams выполняются “лениво”. Это делает конвейерные промежуточные операции неизбежными. С помощью конвейеров, которые в основном представляют собой промежуточные операции, объединенные по порядку, ленивое выполнение становится возможным. Конвейеры помогают отслеживать промежуточные операции, которые необходимо выполнить после того, How наконец встретится терминальная операция.

Заключение

Давайте теперь подведем итог тому, что мы сегодня изучor. В этой статье:
  1. Мы кратко рассмотрели, что такое потоки Java (Java Stream).
  2. Затем мы узнали множество различных методов создания потоков Java в Java 8.
  3. Мы изучor два основных типа операций (промежуточные операции и терминальные операции), которые можно выполнять с потоками Java.
  4. Затем мы подробно рассмотрели несколько примеров How промежуточных, так и терминальных операций.
  5. В итоге мы более подробно узнали о ленивых вычислениях и, наконец, изучor конвейерную обработку в потоках Java.
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION