应使用 Stream.of(list1, list2, list3).flatMap(List::stream).collect(Collectors.toList()) 合并三个及以上 List;两个 List 时用 Stream.concat(a.stream(), b.stream()).collect(Collectors.toList()) 更清晰安全。

用 Stream.concat 合并两个 List,别硬套三个以上
Stream.concat 本质是二元操作,只接受两个 Stream 参数。想拼三个 List?直接链式调用 Stream.concat(Stream.concat(a, b), c) 不但难读,还容易漏掉 .collect(Collectors.toList()) 导致类型不匹配。
常见错误现象:Stream.concat(a, b, c) 编译失败;或只写了 Stream.concat(a, b) 没收集,返回值是 Stream 而非 List,后续调用 .size() 报错。
- 两个
List时,Stream.concat(a.stream(), b.stream()).collect(Collectors.toList())清晰安全 - 三个及以上,优先改用
Stream.of(list1, list2, list3).flatMap(List::stream).collect(Collectors.toList()) -
Stream.concat不会修改原集合,但中间生成的Stream是惰性求值——如果某个List是动态计算或有副作用,顺序和触发时机得小心
addAll 修改原 List 前,先确认是否允许被改
用 list1.addAll(list2) 看似简单,但前提是 list1 是可变的(比如 new ArrayList()),而不能是 Collections.unmodifiableList 或 Arrays.asList() 返回的固定大小列表——否则抛 UnsupportedOperationException 或 ArrayIndexOutOfBoundsException。
使用场景:明确要复用已有容器、且不介意副作用时才选 addAll;若只是临时合并结果,它反而引入了不必要的可变状态。
立即学习“Java免费学习笔记(深入)”;
- 检查来源:
list1 instanceof RandomAccess && !(list1 instanceof ImmutableCollection)不靠谱,直接试list1.add(null)再回滚更实际(仅测试环境) - 避免在多线程中对同一
ArrayList并发调用addAll,没同步会出数据丢失 -
addAll的时间复杂度是 O(n),但底层可能触发多次数组扩容,比纯Stream方案内存开销略低(无中间Stream对象)
性能敏感时,别让 Stream 白走一趟 collect
如果你最终只需要遍历合并后的数据,而不是真要一个新 List 实例,Stream.concat + forEach 或 filter 就够了,省掉 .collect(Collectors.toList()) 这步能减少堆内存分配和 GC 压力。
反例:写成 Stream.concat(a,b).collect(Collectors.toList()).stream().filter(...) —— 先构 List 再转 Stream,纯属自我消耗。
- 确定下游需要随机访问(如
get(5))或多次迭代,才值得collect成List - 小数据量(addAll 通常快 10%–20%,因绕过了
Stream的管道开销 -
Stream.of(...).flatMap在 Java 16+ 有轻微优化,但和addAll比仍多一层函数调用栈
泛型擦除导致的 ClassCastException 隐患
当多个 List 声明类型不同(比如 List<string></string> 和 List<object></object>),用 Stream.concat 合并后若未显式指定目标类型,编译器可能推导出 List<object></object>,运行时往里加 String 没问题,但取出来强转 String 就可能崩——尤其在反射或旧代码混用时。
addAll 在编译期就报错:比如 List<string> a = ...; a.addAll(List<integer>)</integer></string> 直接编译失败,反而更安全。
- 用
Stream.concat时,显式写.collect(Collectors.toList())不够,建议补上类型提示:.collect(Collectors.<string>toList())</string> - 若源头类型不确定,先用
Stream.concat(a.stream(), b.stream()).map(Object::toString)统一转再收口 - IDE 可能不报泛型问题,但 JVM 运行时擦除后只剩
Object,别信“看起来能跑”
List 看似简单,真正卡住人的往往是泛型推导边界、不可变集合误用,还有那种“先 collect 再 stream”的无意识冗余。动手前,先盯住你要的是「新集合」还是「一次遍历」,再决定走哪条路。










