1. 概述

本文将深入探讨 Java 中合并 Stream 的多种实现方式。Stream 合并操作虽然基础,但实际应用中容易踩坑,需要特别注意实现细节。

2. 使用原生 Java 实现

JDK 8 的 Stream 类提供了一些实用的静态工具方法,我们重点分析 concat() 方法的使用场景。

2.1 合并两个 Stream

最简单的合并方式是使用静态方法 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 合并多个 Stream

当需要合并超过两个 Stream 时,操作会变得复杂。以下是几种实现方式:

方案一:嵌套调用(不推荐)

@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()));
}

⚠️ 这种方式在 Stream 数量较多时会导致代码可读性急剧下降

方案二:flatMap 优化(推荐)

@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()));
}

✅ 实现原理:

  1. 先创建包含多个 Stream 的 Stream(Stream<Stream<Integer>>
  2. 通过 flatMap() 扁平化为单层 Stream

3. 使用 StreamEx 库

StreamEx 是增强 Java 8 Stream 功能的开源库,提供更便捷的 API。

3.1 基础合并操作

使用 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()));
}

💡 提示:若将 resultingStream 声明为 StreamEx 类型,可直接调用 toList() 简化收集操作

3.2 前置合并操作

使用 prepend() 在 Stream 前部插入元素:

@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. 使用 jOOλ 库

jOOλ 是兼容 JDK 8 的增强库,核心抽象是 Seq 接口(有序顺序流)。

4.1 基础合并操作

同样提供 append() 方法:

@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()));
}

💡 提示:将 resultingSeq 声明为 Seq 类型可使用便捷的 toList() 方法

4.2 前置合并操作

prepend() 方法实现前插操作:

@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()));
}

5. 总结

Stream 合并操作的实现方式对比:

方案 适用场景 优势 劣势
原生 Java 简单合并 无需依赖 多流合并代码冗长
StreamEx 复杂链式操作 API 友好 需引入第三方库
jOOλ 需要顺序保证 类型安全增强 并行流支持有限

选择建议

  • 简单场景直接使用 Stream.concat()
  • 复杂合并逻辑推荐 StreamEx 或 jOOλ
  • 注意 Stream 的消费特性(合并后原流不可复用)

原始标题:Merging Streams in Java | Baeldung