CodeGym /課程 /JAVA 25 SELF /join、concat 方法:合併串流

join、concat 方法:合併串流

JAVA 25 SELF
等級 32, 課堂 1
開放

1. Stream.concat:合併兩個串流

在程式設計中經常會遇到這種情況:我們有兩個(或更多)資料串流,想把它們合併成一個。例如來自不同分組的兩個學生名單——需要一次性處理全部。在「早期」(Stream API 之前),我們會直接用 addAll 把兩個清單合併。但如果我們正在使用串流(Stream<T>),就會希望用更延遲、表意更清晰的方式來做。

語法與工作原理

最基本的合併兩個串流的方法——使用靜態方法 Stream.concat:

Stream<T> Stream.concat(Stream<? extends T> a, Stream<? extends T> b)

會發生什麼:

  • 先處理第一個串流,接著處理第二個。
  • 輸出是一個新的串流:會先依序輸出第一個串流的所有元素,然後是第二個串流的所有元素。
  • 合併是延遲的:在你對結果串流執行終端操作(例如 forEach 或 collect)之前,不會進行任何處理。

範例:合併兩個姓名清單

import java.util.List;
import java.util.stream.Stream;

List<String> groupA = List.of("Anya", "Boris", "Vika");
List<String> groupB = List.of("Grisha", "Dasha");

Stream<String> allStudents = Stream.concat(groupA.stream(), groupB.stream());

allStudents.forEach(System.out::println);

結果:

Anya
Boris
Vika
Grisha
Dasha

重要:合併後的結果串流是一次性的。和任何 Stream 一樣,不能重複使用。

Stream.concat 的特點

  • 一次只能兩個串流。如果有三個以上——要嘛用多次 concat 串接,要嘛用其他做法(見下文)。
  • 順序會被保留。先是第一個串流的元素,然後是第二個。
  • 延遲合併。如果第一個串流是無限的,就永遠不會走到第二個。
  • 常見 bug:試圖把串流與自身合併(Stream.concat(stream, stream))會導致重複使用同一個串流——這是被禁止的。

2. 合併超過兩個串流:flatMap 與 Stream.of

當串流多於兩個時,使用 Stream.of + flatMap 的組合更方便。

使用 Stream.of + flatMap

import java.util.Collection;
import java.util.List;
import java.util.stream.Stream;

List<String> groupA = List.of("Anya", "Boris");
List<String> groupB = List.of("Vika");
List<String> groupC = List.of("Grisha", "Dasha");

Stream<String> allStudents = Stream.of(groupA, groupB, groupC)
    .flatMap(Collection::stream);

allStudents.forEach(System.out::println);

這裡發生了什麼:

  • Stream.of(groupA, groupB, groupC) 建立一個包含三個集合的串流。
  • flatMap(Collection::stream) 將每個集合展平成同一個字串串流。
  • 結果是——一個包含所有學生的單一串流。

優點:可以合併任意多個串流。

另一種做法:合併串流清單

import java.util.List;
import java.util.stream.Stream;

List<Stream<String>> streams = List.of(
    Stream.of("a", "b"),
    Stream.of("c"),
    Stream.of("d", "e")
);

Stream<String> merged = streams.stream()
    .flatMap(s -> s);

merged.forEach(System.out::println);

結果:

a
b
c
d
e

3. Collectors.joining:用分隔符把字串接起來

有時不只是要合併串流,而是要把所有元素黏成一條字串——例如用逗號把名字串起來。這時就可以使用收集器 Collectors.joining。

語法

String result = stream.collect(Collectors.joining(", "));
  • 不帶參數:會直接把字串接在一起,不插入分隔符。
  • 帶分隔符:會在元素之間插入指定的字串。
  • 帶前綴與後綴: Collectors.joining(delimiter, prefix, suffix)

範例:把學生清單輸出為單一字串

import java.util.List;
import java.util.stream.Collectors;

List<String> students = List.of("Anya", "Boris", "Vika");

String line = students.stream()
    .collect(Collectors.joining(", "));

System.out.println("學生名單: " + line);

結果:

學生名單: Anya, Boris, Vika

範例:帶前綴與後綴

String line = students.stream()
    .collect(Collectors.joining(", ", "[", "]"));

System.out.println(line);

結果:

[Anya, Boris, Vika]

為什麼需要這個?

  • 生成 CSV 字串。
  • 漂亮地列印清單與報表。
  • 將資料組合成單一字串(例如用於 URL 或日誌)。

4. concat 與 flatMap 的比較:何時使用哪一個?

  • Stream.concat——當剛好只有兩個串流且需要保留順序時。
  • Stream.of + flatMap——當串流很多,或它們存放在集合中時。
  • Collectors.joining——當元素型別是 String,且需要一條字串作為結果時。

比較表

方法 適用情境 程式碼範例
Stream.concat(a, b)
兩個串流
Stream.concat(a, b)
Stream.of(...).flatMap()
多個串流、串流集合
Stream.of(a, b, c).flatMap(x -> x)
Collectors.joining()
將元素接成單一字串
stream.collect(Collectors.joining(", "))

5. 教學應用中的實例

範例 1. 合併兩個學院的學生

import java.util.List;
import java.util.stream.Stream;

List<String> itStudents = List.of("Anya", "Boris");
List<String> mathStudents = List.of("Vika", "Grisha");

Stream<String> all = Stream.concat(itStudents.stream(), mathStudents.stream());

all.forEach(System.out::println);

範例 2. 合併所有分組的學生,並輸出為單一字串

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

List<List<String>> allGroups = List.of(
    List.of("Anya", "Boris"),
    List.of("Vika"),
    List.of("Grisha", "Dasha")
);

String allNames = allGroups.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.joining("; "));

System.out.println("所有學生: " + allNames);

結果:

所有學生: Anya; Boris; Vika; Grisha; Dasha

範例 3. 合併數字串流

import java.util.stream.Stream;

Stream<Integer> s1 = Stream.of(1, 2, 3);
Stream<Integer> s2 = Stream.of(4, 5);

Stream<Integer> merged = Stream.concat(s1, s2);

merged.forEach(System.out::print); // 12345

6. 重要細節與注意事項

concat 只能處理兩個串流

對於三個串流會得到巢狀的寫法——可行,但很冗長。更好的方式是使用 Stream.of + flatMap:

Stream<Integer> s1 = Stream.of(1);
Stream<Integer> s2 = Stream.of(2);
Stream<Integer> s3 = Stream.of(3);

Stream<Integer> merged = Stream.concat(Stream.concat(s1, s2), s3);

串流是一次性的

執行過終端操作之後,串流就不能再使用了。重複使用會導致 IllegalStateException。

concat 產生的串流是延遲的

如果第一個串流是無限的(例如透過 Stream.generate(...) 建立),就永遠不會走到第二個。

元素順序

順序總是被保留:先第一個串流,後第二個串流。

Collectors.joining 只適用於 Stream<String>

對非字串的元素,請先轉成字串,例如透過 map(Object::toString):

import java.util.stream.Collectors;
import java.util.stream.Stream;

Stream<Integer> numbers = Stream.of(1, 2, 3);
String line = numbers
    .map(Object::toString)
    .collect(Collectors.joining(", "));

System.out.println(line); // 1, 2, 3

與「手動」做法的比較

在 Stream API 之前:

import java.util.ArrayList;
import java.util.List;

List<String> merged = new ArrayList<>(listA);
merged.addAll(listB);

使用 Stream API:

import java.util.stream.Collectors;

List<String> merged = Stream.concat(listA.stream(), listB.stream())
    .collect(Collectors.toList());

使用串流的好處:你可以在合併之前或之後串接中間操作(filter、map 等),也能與各種資料來源搭配。

7. 合併串流時的常見錯誤

錯誤 1:重複使用同一個串流。 串流只能使用一次。不可把同一個串流與自身合併。

import java.util.stream.Stream;

Stream<String> s = Stream.of("a", "b");
Stream<String> merged = Stream.concat(s, s); // 錯誤!s 會被重複使用

錯誤 2:試圖將無限串流與有限串流合併。 如果第一個串流是無限的,第二個永遠不會開始。

import java.util.stream.Stream;

Stream<Integer> infinite = Stream.generate(() -> 1);
Stream<Integer> finite = Stream.of(2, 3);

Stream<Integer> merged = Stream.concat(infinite, finite);
// merged.limit(10).forEach(System.out::println); // 有限串流不會出現在結果中

錯誤 3:對非字串使用 joining。 收集器 Collectors.joining 需要字串串流。對數字請先轉成字串:

import java.util.List;
import java.util.stream.Collectors;

List<Integer> numbers = List.of(1, 2, 3);
// numbers.stream().collect(Collectors.joining(", ")); // 編譯錯誤!

String joined = numbers.stream()
    .map(Object::toString)
    .collect(Collectors.joining(", "));

錯誤 4:順序被破壞。 如果順序很重要,請使用 concat 或維持順序的 flatMap。像 unordered() 這類方法可能會破壞可預期的輸出順序。

1
任務
JAVA 25 SELF, 等級 32, 課堂 1
上鎖
盛大水果沙拉的採收 🍎🍊
盛大水果沙拉的採收 🍎🍊
1
任務
JAVA 25 SELF, 等級 32, 課堂 1
上鎖
為宇宙天體設計展示櫥窗 🚀
為宇宙天體設計展示櫥窗 🚀
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION