<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rinat</title>
    <description>The latest articles on DEV Community by Rinat (@rinat_mambetov).</description>
    <link>https://dev.to/rinat_mambetov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F112436%2F5b866d1a-7508-45bc-8dd0-f843d88ffbca.jpg</url>
      <title>DEV Community: Rinat</title>
      <link>https://dev.to/rinat_mambetov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rinat_mambetov"/>
    <language>en</language>
    <item>
      <title>java</title>
      <dc:creator>Rinat</dc:creator>
      <pubDate>Wed, 13 Nov 2024 07:57:04 +0000</pubDate>
      <link>https://dev.to/rinat_mambetov/java-157g</link>
      <guid>https://dev.to/rinat_mambetov/java-157g</guid>
      <description></description>
    </item>
    <item>
      <title>Руководство по Java 8 Stream API</title>
      <dc:creator>Rinat</dc:creator>
      <pubDate>Mon, 03 Jun 2024 12:33:08 +0000</pubDate>
      <link>https://dev.to/rinat_mambetov/rukovodstvo-po-java-8-stream-api-4nnn</link>
      <guid>https://dev.to/rinat_mambetov/rukovodstvo-po-java-8-stream-api-4nnn</guid>
      <description>&lt;p&gt;&lt;a href="https://www.baeldung.com/java-8-streams"&gt;оригинал&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Обзор
&lt;/h2&gt;

&lt;p&gt;В этом руководстве рассматривается практическое использование Java 8 Streams от создания до параллельного выполнения.&lt;/p&gt;

&lt;p&gt;Чтобы понять этот материал, читателям необходимо иметь базовые знания о Java 8 (лямбда-выражения, Optional, ссылки на методы).&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Создание потока
&lt;/h2&gt;

&lt;p&gt;В Java существует множество способов создания экземпляра потока (Stream) из различных источников данных.&lt;/p&gt;

&lt;p&gt;После создания экземпляра потока, он не изменяет исходный источник данных. Это означает, что любые операции, выполняемые над потоком (например, фильтрация, маппинг, сортировка), не влияют на сам исходный набор данных. Это обеспечивает безопасность данных и предотвращает нежелательные побочные эффекты.&lt;/p&gt;

&lt;p&gt;Благодаря тому, что потоки не модифицируют исходные данные, из одного и того же источника можно создать несколько потоков. Это позволяет одновременно выполнять различные операции обработки данных над одним и тем же набором данных без изменения исходных данных.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.1. Пустой поток
&lt;/h3&gt;

&lt;p&gt;Мы должны использовать метод empty() для создания пустого потока:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; streamEmpty = Stream.empty();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Мы часто используем метод empty() при создании, чтобы избежать возврата null для потоков без элементов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public Stream&amp;lt;String&amp;gt; streamOf(List&amp;lt;String&amp;gt; list) {
    return list == null || list.isEmpty() ? Stream.empty() : list.stream();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.2. Поток коллекции
&lt;/h3&gt;

&lt;p&gt;Мы также можем создать поток из любого типа Collection (Collection, List, Set):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Collection&amp;lt;String&amp;gt; collection = Arrays.asList("a", "b", "c");
Stream&amp;lt;String&amp;gt; streamOfCollection = collection.stream();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.3. Поток массива
&lt;/h3&gt;

&lt;p&gt;Массив также может быть источником потока:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; streamOfArray = Stream.of("a", "b", "c");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Мы также можем создать stream из существующего массива или части массива:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String[] arr = new String[]{"a", "b", "c"};
Stream&amp;lt;String&amp;gt; streamOfArrayFull = Arrays.stream(arr);
Stream&amp;lt;String&amp;gt; streamOfArrayPart = Arrays.stream(arr, 1, 3); // "b" "c"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.4. Stream.builder()
&lt;/h3&gt;

&lt;p&gt;Желаемый тип должен быть дополнительно указан в правой части инструкции,Когда используется builder, в противном случае метод build() создаст экземпляр Stream&amp;lt;Object&amp;gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; streamBuilder =
  Stream.&amp;lt;String&amp;gt;builder().add("a").add("b").add("c").build();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.5. Stream.generate()
&lt;/h3&gt;

&lt;p&gt;Метод generate() принимает Supplier&amp;lt;T&amp;gt; для генерации элемента. Поскольку результирующий поток бесконечен, разработчик должен указать желаемый размер, иначе метод generate() будет работать до тех пор, пока не достигнет предела памяти:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; streamGenerated =
  Stream.generate(() -&amp;gt; "element").limit(10);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Приведенный выше код создает последовательность из десяти строк со значением "element".&lt;/p&gt;

&lt;h3&gt;
  
  
  2.6. Stream.iterate()
&lt;/h3&gt;

&lt;p&gt;Другой способ создания бесконечного потока - это использование метода iterate():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;Integer&amp;gt; streamIterated = Stream.iterate(40, n -&amp;gt; n + 2).limit(20);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Первый элемент результирующего stream является первым параметром метода iterate(). При создании каждого следующего элемента указанная функция применяется к предыдущему элементу. В приведенном выше примере вторым элементом будет 42.&lt;/p&gt;

&lt;h3&gt;
  
  
  2.7. Поток примитивов
&lt;/h3&gt;

&lt;p&gt;Java 8 предлагает возможность создавать потоки из трех примитивных типов: int, long и double. Поскольку Stream&amp;lt;T&amp;gt; является универсальным интерфейсом, и нет способа использовать примитивы в качестве параметра типа с generics, были созданы три новых специальных интерфейса: IntStream, LongStream, DoubleStream.&lt;/p&gt;

&lt;p&gt;Использование новых интерфейсов устраняет ненужную автоматическую упауовку, что позволяет повысить производительность:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IntStream intStream = IntStream.range(1, 3);
LongStream longStream = LongStream.rangeClosed(1, 3);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Метод range(int startInclusive, int endExclusive) создает упорядоченный поток от первого параметра ко второму параметру. Оно увеличивает значение последующих элементов с шагом, равным 1. Результат не включает последний параметр, это просто верхняя граница последовательности.&lt;/p&gt;

&lt;p&gt;Метод rangeClosed(int startInclusive, int endInclusive)  выполняет то же самое, только с одним отличием, включен второй элемент. Мы можем использовать эти два метода для генерации любого из трех типов потоков примитивов.&lt;/p&gt;

&lt;p&gt;Начиная с Java 8, класс Random предоставляет широкий спектр методов для генерации потоков примитивов. Например, следующий код создает DoubleStream, который состоит из трех элементов:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Random random = new Random();
DoubleStream doubleStream = random.doubles(3);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.8. Поток строк
&lt;/h3&gt;

&lt;p&gt;Мы также можем использовать String в качестве источника для создания потока с помощью метода chars() класса String. Поскольку в JDK нет интерфейса для CharStream , мы используем IntStream вместо этого для представления потока символов.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IntStream streamOfChars = "abc".chars();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Следующий пример разбивает строку на подстроки в соответствии с указанным регулярным выражением:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; streamOfString =
  Pattern.compile(", ").splitAsStream("a, b, c");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2.9. Поток файлов
&lt;/h3&gt;

&lt;p&gt;Кроме того, Java NIO класс Files позволяет нам генерировать Stream&amp;lt;String&amp;gt; текстового файла с помощью метода lines(). Каждая строка текста становится элементом потока:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Path path = Paths.get("C:\\file.txt");
Stream&amp;lt;String&amp;gt; streamOfStrings = Files.lines(path);
Stream&amp;lt;String&amp;gt; streamWithCharset =
  Files.lines(path, Charset.forName("UTF-8"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Кодировка может быть указана в качестве аргумента метода lines().&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Ссылка на поток
&lt;/h2&gt;

&lt;p&gt;Мы можем создать экземпляр потока и иметь доступную ссылку на него, если вызываются только промежуточные операции. Выполнение терминальной операции делает поток недоступным.&lt;/p&gt;

&lt;p&gt;Чтобы продемонстрировать это, мы ненадолго забудем, что наилучшей практикой является объединение последовательности операций в цепочку. Помимо ненужной многословности, технически следующий код допустим:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; stream =
  Stream.of("a", "b", "c").filter(element -&amp;gt; element.contains("b"));
Optional&amp;lt;String&amp;gt; anyElement = stream.findAny();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Однако попытка повторно использовать ту же ссылку после вызова операции терминала вызовет исключение IllegalStateException:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; firstElement = stream.findFirst();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Поскольку исключение IllegalStateException является исключением RuntimeException, компилятор не будет сигнализировать о проблеме. Поэтому очень важно помнить, что потоки Java 8 нельзя использовать повторно.&lt;/p&gt;

&lt;p&gt;Такое поведение логично. Мы разработали streams для применения конечной последовательности операций к источнику элементов в функциональном стиле, а не для хранения элементов.&lt;/p&gt;

&lt;p&gt;Итак, чтобы предыдущий код работал должным образом, необходимо внести некоторые изменения:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;String&amp;gt; elements =
  Stream.of("a", "b", "c").filter(element -&amp;gt; element.contains("b"))
    .collect(Collectors.toList());
Optional&amp;lt;String&amp;gt; anyElement = elements.stream().findAny();
Optional&amp;lt;String&amp;gt; firstElement = elements.stream().findFirst();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Потоковый конвейер
&lt;/h2&gt;

&lt;p&gt;Для выполнения последовательности операций над элементами источника данных и агрегирования их результатов нам понадобятся три части: исходный код, промежуточные операции и терминальная операция.&lt;/p&gt;

&lt;p&gt;Промежуточные операции возвращают новый измененный поток. Например, чтобы создать новый поток вместо существующего без нескольких элементов, следует использовать метод skip():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; onceModifiedStream =
  Stream.of("abcd", "bbcd", "cbcd").skip(1);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если нам нужно больше одной модификации, мы можем связать промежуточные операции. Давайте предположим, что нам также нужно заменить каждый элемент текущего Stream&amp;lt;String&amp;gt; подстрокой из первых нескольких символов. Мы можем сделать это, объединив методы skip() и map():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;String&amp;gt; twiceModifiedStream =
  stream.skip(1).map(element -&amp;gt; element.substring(0, 3));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Поток сам по себе ничего не стоит; пользователя интересует результат операции терминала, который может быть значением некоторого типа или действием, применяемым к каждому элементу потока. Мы можем использовать только одну терминальную операцию для каждого потока.&lt;/p&gt;

&lt;p&gt;Правильный и наиболее удобный способ использования потоков - это конвейер потока, который представляет собой цепочку из источника потока, промежуточных операций и терминальной операции:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = Arrays.asList("abc1", "abc2", "abc3");
long size = list.stream().skip(1)
  .map(element -&amp;gt; element.substring(0, 3)).sorted().count();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  5. Отложенный вызов
&lt;/h2&gt;

&lt;p&gt;Промежуточные операции являются ленивыми. Это означает, что они будут вызываться только в том случае, если это необходимо для выполнения терминальной операции.&lt;/p&gt;

&lt;p&gt;Например, давайте вызовем метод wasCalled(), который увеличивает внутренний счетчик при каждом вызове:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private long counter;

private void wasCalled() {
    counter++;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Теперь давайте вызовем метод, который был вызван() из операции filter():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = Arrays.asList(“abc1”, “abc2”, “abc3”);
counter = 0;
Stream&amp;lt;String&amp;gt; stream = list.stream().filter(element -&amp;gt; {
    wasCalled();
    return element.contains("2");
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Поскольку у нас есть источник из трех элементов, мы можем предположить, что метод filter() будет вызван три раза, а значение переменной counter будет равно 3. Однако выполнение этого кода вообще не изменяет счетчик , он по-прежнему равен нулю, поэтому метод filter() даже не был вызван ни разу. Причина, это отсутствие терминальной операции.&lt;/p&gt;

&lt;p&gt;Давайте немного перепишем этот код, добавив операцию map() и терминальную операцию, findFirst(). Мы также добавим возможность отслеживать порядок вызовов методов с помощью ведения журнала:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Optional&amp;lt;String&amp;gt; stream = list.stream().filter(element -&amp;gt; {
    log.info("filter() was called");
    return element.contains("2");
}).map(element -&amp;gt; {
    log.info("map() was called");
    return element.toUpperCase();
}).findFirst();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Результирующий журнал показывает, что мы дважды вызывали метод filter() и один раз метод map() . Это потому, что конвейер выполняется вертикально. В нашем примере первый элемент stream не удовлетворял предикату filter. Затем мы вызвали метод filter() для второго элемента, который прошел фильтр. Не вызывая filter() для третьего элемента, мы перешли по конвейеру к методу map().&lt;/p&gt;

&lt;p&gt;Операция findFirst() удовлетворяет только одному элементу. Итак, в этом конкретном примере отложенный вызов позволил нам избежать двух вызовов метода, одного для filter() (для третьего элемента) и одного для map() (для первого элемента).&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Порядок выполнения
&lt;/h2&gt;

&lt;p&gt;С точки зрения производительности, правильный порядок является одним из наиболее важных аспектов операций объединения в цепочку в конвейере stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;long size = list.stream().map(element -&amp;gt; {
    wasCalled();
    return element.substring(0, 3);
}).skip(2).count();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Выполнение этого кода увеличит значение счетчика на три. Это означает, что мы вызвали метод stream три раза, но значение size равно единице. Итак, результирующий поток содержит только один элемент, и мы выполнили дорогостоящие операции map() без причины два раза из трех.&lt;/p&gt;

&lt;p&gt;Если мы изменим порядок методов skip() и map(), то счетчик увеличится всего на единицу. Итак, мы вызовем метод map() только один раз:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;long size = list.stream().skip(2).map(element -&amp;gt; {
    wasCalled();
    return element.substring(0, 3);
}).count();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Это подводит нас к следующему правилу: промежуточные операции, которые уменьшают размер потока, должны быть размещены перед операциями, которые применяются к каждому элементу. Итак, нам нужно сохранить такие методы, как skip(), filter(), и distinct() в верхней части нашего конвейера stream.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Сокращение потока
&lt;/h2&gt;

&lt;p&gt;API имеет множество терминальных операций, которые сводят поток к типу или примитиву: count(), max(), min(), и sum(). Однако эти операции работают в соответствии с предопределенной реализацией. Ну и что, если разработчику необходимо настроить механизм сокращения потока? Есть два метода, которые позволяют нам сделать это, методы reduce() и collect().&lt;/p&gt;

&lt;h3&gt;
  
  
  7.1. Метод reduce() (объединение)
&lt;/h3&gt;

&lt;p&gt;Существует три варианта этого метода, которые отличаются своими сигнатурами и типами возвращаемых данных. Они могут иметь следующие параметры:&lt;/p&gt;

&lt;p&gt;identity – начальное значение для накопителя или значение по умолчанию, если поток пуст и накапливать нечего&lt;/p&gt;

&lt;p&gt;accumulator – функция, которая определяет логику агрегирования элементов. Поскольку accumulator создает новое значение для каждого шага объединения, количество новых значений равно размеру потока, и полезно только последнее значение. Это не очень хорошо сказывается на производительности.&lt;/p&gt;

&lt;p&gt;combiner – функция, которая агрегирует результаты сумматора. Мы вызываем combiner только в параллельном режиме, чтобы объединить результаты accumulators из разных потоков.&lt;/p&gt;

&lt;p&gt;Теперь давайте посмотрим на эти три метода в действии:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OptionalInt reduced =
  IntStream.range(1, 4).reduce((a, b) -&amp;gt; a + b); // 6 (1 + 2 + 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int reducedTwoParams =
  IntStream.range(1, 4).reduce(10, (a, b) -&amp;gt; a + b); // 16 (10 + 1 + 2 + 3)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int reducedParams = Stream.of(1, 2, 3)
  .reduce(10, (a, b) -&amp;gt; a + b, (a, b) -&amp;gt; {
     log.info("combiner was called");
     return a + b;
  });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Результат будет таким же, как в предыдущем примере (16), и логирования не будет, что означает, что combiner не вызывался. Чтобы combiner работал, поток должен быть параллельным:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int reducedParallel = Arrays.asList(1, 2, 3).parallelStream()
    .reduce(10, (a, b) -&amp;gt; a + b, (a, b) -&amp;gt; {
       log.info("combiner was called");
       return a + b;
    });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Результат здесь другой (36), и combiner вызывался дважды. Здесь объединение выполняется по следующему алгоритму: накопитель запускается три раза путем добавления каждого элемента потока к identity. Эти действия выполняются параллельно. В результате у них получилось (10 + 1 = 11; 10 + 2 = 12; 10 + 3 = 13;). Теперь combiner может объединить эти три результата. Для этого требуется две итерации (12 + 13 = 25; 25 + 11 = 36).&lt;/p&gt;

&lt;h3&gt;
  
  
  7.2. Метод collect()
&lt;/h3&gt;

&lt;p&gt;Объединение потока также может быть выполнено с помощью другой терминальной операции, метода collect(). Он принимает аргумент типа Collector, который определяет механизм объединения. Для большинства распространенных операций уже созданы предопределенные коллекторы. К ним можно получить доступ с помощью типа Collectors.&lt;/p&gt;

&lt;p&gt;В этом разделе мы будем использовать следующий список в качестве источника для всех потоков:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;Product&amp;gt; productList = Arrays.asList(new Product(23, "potatoes"),
  new Product(14, "orange"), new Product(13, "lemon"),
  new Product(23, "bread"), new Product(13, "sugar"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Преобразование потока в коллекцию (Collection, List или Set):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;String&amp;gt; collectorCollection =
  productList.stream().map(Product::getName).collect(Collectors.toList());
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Объединение в строку:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String listToString = productList.stream().map(Product::getName)
  .collect(Collectors.joining(", ", "[", "]"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Метод joiner() может иметь от одного до трех параметров (разделитель, префикс, суффикс). Самое удобное в использовании joiner() заключается в том, что разработчику не нужно проверять, достигает ли поток своего конца, чтобы применить суффикс, а не разделитель. Коллектор позаботится об этом.&lt;/p&gt;

&lt;p&gt;Обработка среднего значения всех числовых элементов потока:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double averagePrice = productList.stream()
  .collect(Collectors.averagingInt(Product::getPrice));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Обработка суммы всех числовых элементов потока:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int summingPrice = productList.stream()
  .collect(Collectors.summingInt(Product::getPrice));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Методы averagingXX(), summingXX() и summarizingXX() могут работать с примитивами (int, long, double) и с их классами-оболочками (Integer, Long, Double). Еще одной мощной функцией этих методов является обеспечение маппинга. В результате разработчику не нужно использовать дополнительную операцию map() перед методом collect().&lt;/p&gt;

&lt;p&gt;Сбор статистической информации об элементах stream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IntSummaryStatistics statistics = productList.stream()
  .collect(Collectors.summarizingInt(Product::getPrice));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Используя результирующий экземпляр типа IntSummaryStatistics, разработчик может создать статистический отчет, применив метод toString(). Результатом будет строка, общая для этой “IntSummaryStatistics{count=5, sum=86, min=13, average=17200000, max=23}.”&lt;/p&gt;

&lt;p&gt;Также легко извлечь из этого объекта отдельные значения для count, sum, min, и average, применив методы getCount(), getSum(), getMin(), getAverage(), и getMax(). Все эти значения могут быть извлечены из одного конвейера.&lt;/p&gt;

&lt;p&gt;Группировка элементов stream в соответствии с указанной функцией:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Map&amp;lt;Integer, List&amp;lt;Product&amp;gt;&amp;gt; collectorMapOfLists = productList.stream()
  .collect(Collectors.groupingBy(Product::getPrice));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В приведенном выше примере поток был сведен к Map, который группирует все продукты по их цене.&lt;/p&gt;

&lt;p&gt;Разделение элементов stream на группы в соответствии с некоторым предикатом:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Map&amp;lt;Boolean, List&amp;lt;Product&amp;gt;&amp;gt; mapPartioned = productList.stream()
  .collect(Collectors.partitioningBy(element -&amp;gt; element.getPrice() &amp;gt; 15));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Этот код использует потоки данных (Streams) для разделения списка продуктов (productList) на две группы на основе цены продукта. Результатом будет Map, где ключами являются Boolean значения (true или false), а значениями — списки продуктов, соответствующие этим ключам.&lt;/p&gt;

&lt;p&gt;Приведение коллектора к дополнительному преобразованию:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set&amp;lt;Product&amp;gt; unmodifiableSet = productList.stream()
  .collect(Collectors.collectingAndThen(Collectors.toSet(),
  Collections::unmodifiableSet));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;В данном конкретном случае сборщик преобразовал stream в Set, а затем создал из него неизменяемый Set.&lt;/p&gt;

&lt;p&gt;Кастомный сборщик:&lt;/p&gt;

&lt;p&gt;Если по какой-либо причине необходимо создать кастомный коллектор, самый простой и наименее подробный способ сделать это - использовать метод of() типа Collector.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Collector&amp;lt;Product, ?, LinkedList&amp;lt;Product&amp;gt;&amp;gt; toLinkedList =
  Collector.of(LinkedList::new, LinkedList::add,
    (first, second) -&amp;gt; {
       first.addAll(second);
       return first;
    });

LinkedList&amp;lt;Product&amp;gt; linkedListOfPersons =
  productList.stream().collect(toLinkedList);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Метод of используется для создания экземпляра Collector. Первым аргументом является функция, которая создает аккумулятор (в нашем случае, новый LinkedList). Вторым аргументом — функция, которая добавляет элемент в аккумулятор. Третий аргумент — функция, объединяющая два аккумулятора в один. В данном случае, она просто добавляет все элементы из второго аккумулятора в первый. В этом примере экземпляр коллектора был объединен в LinkedList&amp;lt;Product&amp;gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Параллельные потоки
&lt;/h2&gt;

&lt;p&gt;До появления Java 8 распараллеливание было сложным. Появление ExecutorService и forkJoin немного упростило жизнь разработчику, но все же стоило запомнить, как создать конкретный исполнитель, как его запускать и так далее. В Java 8 представлен способ реализации параллелизма в функциональном стиле.&lt;/p&gt;

&lt;p&gt;API позволяет нам создавать параллельные потоки, которые выполняют операции в параллельном режиме. Если источником потока является коллекция или массив, этого можно достичь с помощью метода parallelStream():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Stream&amp;lt;Product&amp;gt; streamOfCollection = productList.parallelStream();
boolean isParallel = streamOfCollection.isParallel();
boolean bigPrice = streamOfCollection
  .map(product -&amp;gt; product.getPrice() * 12)
  .anyMatch(price -&amp;gt; price &amp;gt; 200);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Если источником потока является нечто иное, чем коллекция или массив, следует использовать метод parallel():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IntStream intStreamParallel = IntStream.range(1, 150).parallel();
boolean isParallel = intStreamParallel.isParallel();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;По сути, Stream API автоматически использует платформу forkJoin для параллельного выполнения операций. По умолчанию будет использоваться общий пул потоков.&lt;/p&gt;

&lt;p&gt;При использовании потоков в параллельном режиме избегайте блокирования операций. Также лучше использовать параллельный режим, когда для выполнения задач требуется аналогичное количество времени. Если одна задача длится намного дольше другой, это может замедлить рабочий процесс всего приложения.&lt;/p&gt;

&lt;p&gt;Поток в параллельном режиме может быть преобразован обратно в последовательный режим с помощью метода sequential():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;IntStream intStreamSequential = intStreamParallel.sequential();
boolean isParallel = intStreamSequential.isParallel();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  9. Заключение
&lt;/h2&gt;

&lt;p&gt;Stream API - это мощный, но простой для понимания набор инструментов для обработки последовательности элементов. При правильном использовании это позволяет нам сократить огромное количество шаблонного кода, создавать более читаемые программы и повышать производительность приложения.&lt;/p&gt;

&lt;p&gt;В большинстве примеров кода, показанных в этой статье, мы оставили потоки неиспользованными (мы не применяли метод close() или терминальную операцию). В реальном приложении не оставляйте созданный поток неиспользованным, так как это приведет к утечке памяти.&lt;/p&gt;

</description>
      <category>java</category>
      <category>streams</category>
    </item>
  </channel>
</rss>
