1. 錯誤:重複使用串流
在 Java 中,Stream 是一次性工具。一旦你執行了終端操作(例如 collect()、forEach()、count()),串流就會關閉並且無法再繼續使用。
錯誤範例
Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // 一切正常
stream.forEach(System.out::println); // 會丟出 IllegalStateException!
輸出:
a
b
c
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
如何避免
如果需要執行多個操作——每次都建立新的串流:
List<String> list = List.of("a", "b", "c");
list.stream().forEach(System.out::println); // OK
list.stream().count(); // OK
小撇步: 串流就像餐廳的拋棄式杯子:用完就丟,下一杯請拿新的。
2. 錯誤:在串流處理期間修改集合
如果你在透過串流遍歷時修改原始集合,會得到 ConcurrentModificationException。這在篩選與刪除元素時很常見。
錯誤範例
List<String> names = new ArrayList<>(List.of("Alice", "Bob", "Charlie"));
names.stream().forEach(name -> {
if (name.startsWith("A")) {
names.remove(name); // 砰!
}
});
結果:
Exception in thread "main" java.util.ConcurrentModificationException
正確作法
使用不會在遍歷期間修改集合的方法,或將結果收集到新集合:
names.removeIf(name -> name.startsWith("A")); // OK,removeIf 是安全的
// 或使用 Stream API:
List<String> filtered = names.stream()
.filter(name -> !name.startsWith("A"))
.collect(Collectors.toList());
記住: 串流講究純淨——當串流正在「吃」原集合時,別去動它。
3. 錯誤:在無限串流中忘了加 limit
Stream.iterate 與 Stream.generate 可能會建立無限串流。若忘了用 limit 限制大小,程式可能會永遠卡住(或直到 OutOfMemoryError)。
錯誤範例
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
infinite.forEach(System.out::println); // 程式將永遠不會結束!
正確作法
使用 limit 限制串流:
Stream<Integer> finite = Stream.iterate(0, n -> n + 1).limit(10);
finite.forEach(System.out::println); // 會輸出 0 到 9
建議: 無限串流就像沒有大結局的影集:總得有人喊停,最好是你提前加上限制!
4. 錯誤:對 null 的處理不當
Stream API 不喜歡 null。如果集合中出現 null,許多操作可能會丟出 NullPointerException,尤其是當你在元素上呼叫方法時。
錯誤範例
List<String> names = Arrays.asList("Alice", null, "Bob");
names.stream()
.map(String::toUpperCase) // 砰!遇到 null
.forEach(System.out::println);
結果:
Exception in thread "main" java.lang.NullPointerException
正確作法
先過濾掉 null 值:
names.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.forEach(System.out::println);
搭配 Optional
如果你有 Stream<Optional<T>>,請使用 flatMap(Optional::stream)(Java 9+)來「展開」:
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty(), Optional.of("B"));
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // 只會輸出 "A" 與 "B"
建議: 盡量不要把 null 放進集合;若非得如此,請在一開始就把它們過濾掉。
5. 錯誤:元素順序丟失
某些操作(例如 flatMap、concat)可能會影響元素順序,特別是當你使用平行串流或在合併不同集合時。
錯誤範例
List<Integer> list1 = List.of(1, 2, 3);
List<Integer> list2 = List.of(4, 5, 6);
Stream<Integer> combined = Stream.concat(list1.stream(), list2.stream());
combined.forEach(System.out::print); // 123456,順序不變
// 但如果使用平行串流:
Stream<Integer> par = Stream.concat(list1.parallelStream(), list2.parallelStream());
par.forEach(System.out::print); // 順序可能會出乎意料!
正確作法
如果順序很重要——請避免平行串流,或使用保證順序的方法(forEachOrdered):
par.forEachOrdered(System.out::print);
事實: Stream API 預設會保留順序,除非你自己把它「打亂」了。
6. 錯誤:集合合併的低效作法
對於集合運算,最好使用 Set 而不是 List,以避免重複並提升效能。在可以使用 flatMap 的情況下,別寫巢狀迴圈。
錯誤範例
List<String> list1 = List.of("a", "b", "c");
List<String> list2 = List.of("b", "c", "d");
// 低效:
List<String> union = new ArrayList<>(list1);
for (String s : list2) {
if (!union.contains(s)) { // List 的 contains 是 O(n)
union.add(s);
}
}
// 使用 Set 與 Stream API 的有效作法:
Set<String> unionSet = Stream.concat(list1.stream(), list2.stream())
.collect(Collectors.toSet());
或用於差集:
// 低效:
List<String> diff = new ArrayList<>(list1);
diff.removeAll(list2); // OK,但如果改用 stream 搭配 list.contains,會很慢
// 高效:
Set<String> set2 = new HashSet<>(list2);
List<String> difference = list1.stream()
.filter(s -> !set2.contains(s))
.collect(Collectors.toList());
建議: 需要處理唯一元素時請使用 Set。對大型集合,避免巢狀迴圈,優先考慮 stream + flatMap。
7. 錯誤:重複使用終端操作
有時你可能想把串流「分叉」成兩個分支,例如同時收集成清單又統計數量。但串流是一次性的,第二個操作會導致錯誤。
錯誤範例
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
long count = stream.count(); // 砰!IllegalStateException
正確作法
如果需要多次使用結果——先把它收集成集合:
List<String> list = Stream.of("a", "b", "c").collect(Collectors.toList());
long count = list.size();
建議: 想同時兩邊都要?那就先收集成集合再說。
8. 錯誤:忘記關閉資源(close())
如果你使用與資源相關的串流(例如 Files.lines(Path)),請務必在使用後關閉它們,避免資源洩漏。
錯誤範例
Stream<String> lines = Files.lines(Path.of("file.txt"));
lines.forEach(System.out::println); // OK,但資源沒有被關閉!
正確作法
使用 try-with-resources:
try (Stream<String> lines = Files.lines(Path.of("file.txt"))) {
lines.forEach(System.out::println);
}
建議: 檔案串流就像熱水壺:燒開了就要關!
9. 錯誤:在 flatMap 中錯誤處理 Optional 與 null
處理 List<Optional<T>> 時,很多人會使用 map(Optional::get),當 Optional 內部為空時就會拋出例外。
錯誤範例
List<Optional<String>> optionals = List.of(Optional.of("A"), Optional.empty());
optionals.stream()
.map(Optional::get) // 砰!NoSuchElementException
.forEach(System.out::println);
正確作法
使用 flatMap(Optional::stream)(Java 9+):
optionals.stream()
.flatMap(Optional::stream)
.forEach(System.out::println); // 只會有 "A"
建議: Optional 不只是包裝,而是用來安全處理值缺席的工具。
10. 錯誤:equals/hashCode 實作不正確
集合運算(Set、distinct、聯集、交集)仰賴正確實作的 equals 與 hashCode 方法。
錯誤範例
class Student {
String name;
int age;
// 缺少 equals 與 hashCode!
}
Set<Student> students = new HashSet<>();
students.add(new Student("Alice", 20));
students.add(new Student("Alice", 20));
System.out.println(students.size()); // 2,明明應該視為相同!
正確作法
若要在 Set、Map 中使用,或進行 distinct 等操作,請為你的類別覆寫 equals 與 hashCode。
11. 錯誤:在 collect 之後繼續使用同一個 stream
在執行完終端操作(例如 collect)之後,串流會被關閉。任何嘗試繼續使用同一個串流的行為都會導致錯誤。
錯誤範例
Stream<String> stream = Stream.of("a", "b", "c");
List<String> list = stream.collect(Collectors.toList());
stream.filter(s -> s.startsWith("a")).forEach(System.out::println); // Exception!
正確作法
從集合建立新的串流:
list.stream().filter(s -> s.startsWith("a")).forEach(System.out::println);
12. 錯誤:錯誤使用 parallelStream
許多人以為 parallelStream() 一定會加速。其實對小型集合或簡單操作,平行化可能會因執行緒管理的額外開銷而變慢。
錯誤範例
List<Integer> numbers = IntStream.range(0, 10).boxed().collect(Collectors.toList());
numbers.parallelStream().forEach(System.out::println); // 可能比一般的 stream() 還慢
正確作法
僅在集合足夠大或計算足夠耗時計使用 parallelStream,特別是當操作是純函式(無副作用)且可結合時。
13. 錯誤:忘了呼叫終端運算子
中間操作(map、filter、flatMap 等)在沒有呼叫終端運算子(collect、forEach、count 等)之前不會執行。
錯誤範例
Stream<String> stream = Stream.of("a", "b", "c").map(String::toUpperCase);
// 什麼都不會發生!
正確作法
務必呼叫終端運算子:
stream.forEach(System.out::println);
GO TO FULL VERSION