在日常 Java 开发中,使用 Stream API 处理集合是非常常见的操作。但如果不注意 Stream 返回的列表特性,很容易遇到运行时异常。本文将通过一个具体案例,详细解析这个问题的原因和解决方案。
问题复现
先看一段会引发错误的示例代码:
java
public static void main(String[] args) {
List<OperationalReport> reportList = new ArrayList<>();
OperationalReport report = new OperationalReport();
report.setIndustryCode("0000");
reportList.add(report);
OperationalReport report1 = new OperationalReport();
report1.setIndustryCode("0000");
reportList.add(report1);
// 使用Stream过滤并收集结果
List<OperationalReport> originalReportList = reportList.stream()
.filter(i -> StringUtils.isNotBlank(i.getIndustryName()))
.toList();
// 尝试添加元素,会抛出异常
originalReportList.add(report1); // ❌ 报错:UnsupportedOperationException
}
当执行到originalReportList.add(report1)时,程序会抛出以下异常:
plaintext
Exception in thread "main" java.lang.UnsupportedOperationException
at java.base/java.util.Collections$UnmodifiableCollection.add(Collections.java:1060)
...
问题原因分析
这个错误的根源在于 Java Stream API 的toList()方法的实现细节:
-
Java 8+ 的 Stream.toList () 返回不可变列表
在 Java 16 及以后版本中,Stream.toList()方法返回的是不可变列表(java.util.Collections.UnmodifiableList)。这种列表不支持添加、删除等结构修改操作。 -
与 Java 8 传统收集方式的差异
在 Java 8 中,我们通常使用Collectors.toList()收集 Stream 结果,它返回的是一个可变的 ArrayList:java
// Java 8方式,返回可变列表 List<OperationalReport> list = reportList.stream() .filter(...) .collect(Collectors.toList()); // 返回可变的ArrayList而 Java 16 + 的
toList()方法是为了提供更简洁的语法,但牺牲了列表的可变性。 -
错误触发机制
在示例代码中,虽然原始的reportList是可变的ArrayList,但经过stream().toList()处理后,originalReportList变成了不可变列表。调用add()方法时,不可变列表会直接抛出UnsupportedOperationException。
解决方案
下面提供几种修复这个问题的方法:
-
显式创建可变列表(推荐)
// 使用new ArrayList<>包装Stream结果 List<OperationalReport> originalReportList = new ArrayList<>( reportList.stream() .filter(i -> StringUtils.isNotBlank(i.getIndustryName())) .toList() ); originalReportList.add(report1); // ✅ 现在可以安全添加元素 -
使用 Collectors.toCollection
// 直接指定收集器创建可变列表 List<OperationalReport> originalReportList = reportList.stream() .filter(i -> StringUtils.isNotBlank(i.getIndustryName())) .collect(Collectors.toCollection(ArrayList::new)); // 返回可变ArrayList -
预先创建可变列表
// 预先创建可变列表,避免Stream结果的不可变性 List<OperationalReport> originalReportList = new ArrayList<>(); reportList.stream() .filter(i -> StringUtils.isNotBlank(i.getIndustryName())) .forEach(originalReportList::add); originalReportList.add(report1); // 安全添加
最佳实践建议
-
明确列表可变性需求
在使用 Stream 处理集合时,先明确后续是否需要修改结果列表。如果需要修改,务必使用上述可变列表的创建方式。 -
注意 Java 版本差异
- Java 8-15:使用
Collectors.toList()或Collectors.toCollection(ArrayList::new) - Java 16+:使用
toList()后如需修改,必须显式包装为可变列表
- Java 8-15:使用
-
防御性编程
对于可能被多次使用或传递的列表,建议在创建时就确保其可变性,避免后续操作受限。
总结
理解 Java Stream API 的细节非常重要,特别是像toList()这种看似简单的方法。在实际开发中,我们经常会在过滤、映射后需要对结果列表进行修改,这时如果使用了不可变列表,就会引发运行时异常。通过本文介绍的方法,你可以安全地处理 Stream 结果,避免这类常见错误。
355

被折叠的 条评论
为什么被折叠?



