«1. Обзор

В этой быстрой статье мы объясняем различные способы слияния Java Streams, что не очень интуитивно понятно.

2. Использование Plain Java

Класс JDK 8 Stream имеет несколько полезных статических служебных методов. Давайте подробнее рассмотрим метод concat().

2.1. Объединение двух потоков

Самый простой способ объединить два потока — использовать статический метод Stream.concat():

@Test
public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);

    Stream<Integer> resultingStream = Stream.concat(stream1, stream2);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6),
      resultingStream.collect(Collectors.toList()));
}

2.2. Слияние нескольких потоков

Когда нам нужно объединить более двух потоков, все становится немного сложнее. Одна из возможностей состоит в том, чтобы объединить первые два потока, затем объединить результат со следующим и так далее.

Следующий фрагмент кода показывает это в действии:

@Test
public void given3Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);

    Stream<Integer> resultingStream = Stream.concat(
      Stream.concat(stream1, stream2), stream3);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36),
      resultingStream.collect(Collectors.toList()));
}

Как мы видим, этот подход становится неприемлемым для большего количества потоков. Конечно, мы можем создать промежуточные переменные или вспомогательные методы, чтобы сделать его более читабельным, но вот лучший вариант:

@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);
    Stream<Integer> stream4 = Stream.of(99);

    Stream<Integer> resultingStream = Stream.of(
      stream1, stream2, stream3, stream4)
      .flatMap(i -> i);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
      resultingStream.collect(Collectors.toList()));
}

Здесь происходит следующее:

    Сначала мы создаем новый поток, содержащий 4 потока, которые приводит к Stream\u003cStream\u003cInteger\u003e\u003e Затем мы flatMap() преобразуем это в Stream\u003cInteger\u003e с помощью функции идентификации

3. Использование StreamEx

StreamEx — это библиотека Java с открытым исходным кодом, которая расширяет возможности Java 8. Потоки. Он использует класс StreamEx в качестве расширения интерфейса JDK Stream.

3.1. Слияние потоков

Библиотека StreamEx позволяет нам объединять потоки, используя метод экземпляра append():

@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);
    Stream<Integer> stream4 = Stream.of(99);

    Stream<Integer> resultingStream = StreamEx.of(stream1)
      .append(stream2)
      .append(stream3)
      .append(stream4);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
      resultingStream.collect(Collectors.toList()));
}

Поскольку это метод экземпляра, мы можем легко объединить его и добавить несколько потоков.

Обратите внимание, что мы также можем создать список из потока с помощью toList(), если мы приведем результирующую переменную Stream к типу StreamEx.

3.2. Слияние потоков с помощью prepend()

StreamEx также содержит метод prepend(), добавляющий элементы один перед другим: расширения для JDK. Самая важная абстракция потока здесь называется Seq. Обратите внимание, что это последовательный и упорядоченный поток, поэтому вызов parallel() не будет иметь никакого эффекта.

@Test
public void given3Streams_whenPrepended_thenResultStreamContainsAllElements() {
    Stream<String> stream1 = Stream.of("foo", "bar");
    Stream<String> openingBracketStream = Stream.of("[");
    Stream<String> closingBracketStream = Stream.of("]");

    Stream<String> resultingStream = StreamEx.of(stream1)
      .append(closingBracketStream)
      .prepend(openingBracketStream);

    assertEquals(
      Arrays.asList("[", "foo", "bar", "]"),
      resultingStream.collect(Collectors.toList()));
}

4.1. Слияние потоков

Как и в библиотеке StreamEx, в jOOλ есть метод append():

Также есть удобный метод toList(), если мы приводим результирующую переменную Seq к типу jOOλ Seq.

4.2. Слияние потоков с помощью prepend()

@Test
public void given2Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> seq1 = Stream.of(1, 3, 5);
    Stream<Integer> seq2 = Stream.of(2, 4, 6);

    Stream<Integer> resultingSeq = Seq.ofType(seq1, Integer.class)
      .append(seq2);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6),
      resultingSeq.collect(Collectors.toList()));
}

Как и ожидалось, поскольку существует метод append(), в jOOÎ также есть метод prepend():

5. Заключение

Мы видели, что слияние потоков относительно просто с использованием JDK 8. Когда нам нужно выполнить большое количество слияний, может быть полезно использовать библиотеку StreamEx или jOOÎ для удобочитаемости.

@Test
public void given3Streams_whenPrepending_thenResultStreamContainsAllElements() {
    Stream<String> seq = Stream.of("foo", "bar");
    Stream<String> openingBracketSeq = Stream.of("[");
    Stream<String> closingBracketSeq = Stream.of("]");

    Stream<String> resultingStream = Seq.ofType(seq, String.class)
      .append(closingBracketSeq)
      .prepend(openingBracketSeq);

    Assert.assertEquals(
      Arrays.asList("[", "foo", "bar", "]"),
      resultingStream.collect(Collectors.toList()));
}

Вы можете найти исходный код на GitHub.

«

You can find the source code over on GitHub.