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. 错误:忘记关闭与资源关联的流
如果你使用与资源关联的流(例如 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