Java Streams 中的七個常見錯誤
在使用 Java Streams 時,以下是一些常見的錯誤:
1.不使用終端操作
錯誤:忘記調用終端操作(如collect()
、forEach()
或reduce()
),這會導致流沒有執(zhí)行。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 創(chuàng)建流但沒有調用終端操作
names.stream()
.filter(name -> name.startsWith("A")); // 這里沒有調用終端操作
// 由于流沒有執(zhí)行,什么都不會打印
System.out.println("Stream operations have not been executed.");
}
解決方案:始終以終端操作結束,以觸發(fā)流的處理。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 創(chuàng)建流并調用終端操作
names.stream()
.filter(name -> name.startsWith("A")) // 中間操作
.forEach(System.out::println); // 終端操作
// 這將打印 "Alice",因為流被執(zhí)行了
}
2.修改源數據
錯誤:在處理流時修改源數據結構(如List
)可能導致未知的結果。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 嘗試在流處理時修改源列表
names.stream()
.filter(name -> {
if (name.startsWith("B")) {
names.remove(name); // 修改源列表
}
return true;
})
.forEach(System.out::println);
// 由于并發(fā)修改,輸出可能不符合預期
System.out.println("Remaining names: " + names);
}
解決方案:不要在流操作期間修改源數據,而是使用流創(chuàng)建新的集合。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 基于過濾結果創(chuàng)建一個新列表
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("B")) // 過濾出以 'B' 開頭的名字
.collect(Collectors.toList());
// 顯示過濾后的列表
System.out.println("Filtered names: " + filteredNames);
System.out.println("Original names remain unchanged: " + names);
}
3.忽略并行流的開銷
錯誤:認為并行流總是能提高性能,而不考慮上下文,例如小數據集或輕量級操作。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); // 小數據集
// 在小數據集上使用并行流
numbers.parallelStream()
.map(n -> {
// 模擬輕量級操作
System.out.println(Thread.currentThread().getName() + " processing: " + n);
return n * n;
})
.forEach(System.out::println);
// 輸出可能顯示為簡單任務創(chuàng)建了不必要的線程
}
解決方案:謹慎使用并行流,尤其是對于大數據集的 CPU 密集型任務。
public static void main(String[] args) {
List<Integer> numbers = IntStream.rangeClosed(1, 1_000_000) // 大數據集
.boxed()
.collect(Collectors.toList());
// 在大數據集上使用并行流進行 CPU 密集型操作
List<Integer> squareNumbers = numbers.parallelStream()
.map(n -> {
// 模擬 CPU 密集型操作
return n * n;
})
.collect(Collectors.toList());
// 打印前 10 個結果
System.out.println("First 10 squared numbers: " + squareNumbers.subList(0, 10));
}
4.過度使用中間操作
錯誤:鏈式調用過多的中間操作(如filter()
和map()
)可能會引入性能開銷。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 過度使用中間操作
List<String> result = names.stream()
.filter(name -> name.startsWith("A")) // 第一個中間操作
.filter(name -> name.length() > 3) // 第二個中間操作
.map(String::toUpperCase) // 第三個中間操作
.map(name -> name + " is a name") // 第四個中間操作
.toList(); // 終端操作
// 輸出結果
System.out.println(result);
}
解決方案:盡量減少流管道中的中間操作,并在可能的情況下使用流融合。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Eve");
// 優(yōu)化流管道
List<String> result = names.stream()
.filter(name -> name.startsWith("A") && name.length() > 3) // 將過濾器合并為一個
.map(name -> name.toUpperCase() + " is a name") // 合并 map 操作
.toList(); // 終端操作
// 輸出結果
System.out.println(result);
}
5.不處理 Optional 值
錯誤:在使用findFirst()
或reduce()
等操作時,沒有正確處理Optional
結果。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 嘗試查找以 "Z" 開頭的名字(不存在)
String firstNameStartingWithZ = names.stream()
.filter(name -> name.startsWith("Z"))
.findFirst() // 返回一個 Optional
.get(); // 如果 Optional 為空,這將拋出 NoSuchElementException
// 輸出結果
System.out.println(firstNameStartingWithZ);
}
解決方案:在訪問Optional
的值之前,始終檢查它是否存在,以避免NoSuchElementException
。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// 正確處理 Optional
Optional<String> firstNameStartingWithZ = names.stream()
.filter(name -> name.startsWith("Z"))
.findFirst(); // 返回一個 Optional
// 檢查 Optional 是否存在
if (firstNameStartingWithZ.isPresent()) {
System.out.println(firstNameStartingWithZ.get());
} else {
System.out.println("No name starts with 'Z'");
}
}
6.忽略線程安全
錯誤:在并行流中使用共享的可變狀態(tài)可能導致競態(tài)條件和不一致的結果。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> results = new ArrayList<>(); // 共享的可變狀態(tài)
// 在并行流中使用共享的可變狀態(tài)
numbers.parallelStream().forEach(number -> {
results.add(number * 2); // 這可能導致競態(tài)條件
});
// 輸出結果
System.out.println("Results: " + results);
}
解決方案:避免共享可變狀態(tài);使用線程安全的集合或局部變量。
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> results = new CopyOnWriteArrayList<>(); // 線程安全的集合
// 在并行流中使用線程安全的集合
numbers.parallelStream().forEach(number -> {
results.add(number * 2); // 避免競態(tài)條件
});
// 輸出結果
System.out.println("Results: " + results);
}
7.混淆中間操作和終端操作
錯誤:不清楚中間操作(返回新流)和終端操作(產生結果)之間的區(qū)別。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 錯誤:嘗試將中間操作用作終端操作
// 這將無法編譯,因為 'filter' 返回一個 Stream,而不是一個 List
names.stream().filter(name -> name.startsWith("A")).forEach(System.out::println); // 這里正確使用了終端操作
}
解決方案:熟悉每種操作類型的特性,以避免代碼中的邏輯錯誤。
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
// 正確使用中間操作和終端操作
List<String> filteredNames = names.stream()
.filter(name -> name.startsWith("A")) // 中間操作
.collect(Collectors.toList()); // 終端操作
// 輸出過濾后的名字
System.out.println("Filtered Names: " + filteredNames);
}
通過掌握這些技巧并實施這些解決方案,你可以更好地使用 Java Streams,并編寫更簡潔、更高效的代碼。