Collections.nCopies() 返回不可变的 CopiesList,不支持增删改操作;正确用法是传入 ArrayList 构造器创建可变副本,但需注意对象共享、性能及序列化等陷阱。

为什么 Collections.nCopies() 不能直接当可变集合用
调用 Collections.nCopies(5, "a") 返回的是一个不可变的 List 实现(内部是 CopiesList),它不支持 add()、remove()、set() 等修改操作,一旦尝试就会抛出 UnsupportedOperationException。这不是 bug,而是设计使然——它只负责“重复生成”,不承担“可变容器”职责。
所以常见错误是直接拿它当普通 List 用:
List<string> list = Collections.nCopies(3, "x");
list.add("y"); // 运行时抛出 UnsupportedOperationException</string>
正确做法是把它作为数据源,交给真正可变的集合构造器处理。
用 ArrayList 构造器完成初始化的两种写法
核心思路:把 Collections.nCopies() 的返回值传给 ArrayList 构造器,由后者拷贝一份可变副本。
-
new ArrayList(Collections.nCopies(n, value))—— 最常用,语义清晰,适合大多数场景 -
new ArrayList(List.copyOf(Collections.nCopies(n, value)))(Java 10+)—— 多一层包装,没必要,反而增加开销
示例:
List<integer> numbers = new ArrayList(Collections.nCopies(4, 99)); numbers.set(2, 100); // ✅ 成功,现在是 [99, 99, 100, 99]</integer>
注意:如果 value 是可变对象(比如 new HashMap()),所有元素会引用同一个实例,后续修改会相互影响——这是深拷贝缺失导致的典型陷阱。
和循环添加比,性能差在哪?
表面上看,new ArrayList(Collections.nCopies(n, value)) 更简洁,但实际性能不一定更好:
-
Collections.nCopies()返回的CopiesList是懒加载的,不立即分配数组;但ArrayList构造器会遍历整个视图并逐个add,触发完整复制 - 相比手动
for循环 +list.add(value),它多了迭代器创建、类型检查等开销 - 若已知大小,更高效的方式是预设容量:
new ArrayList(n) { { for (int i = 0; i (双大括号初始化,慎用于频繁调用场景)
简单初始化几十个元素时差异可忽略;上万级且对吞吐敏感时,建议压测对比。
替代方案:什么时候该换别的方法
Collections.nCopies() + ArrayList 构造器只适合「固定值、不可变对象、中小规模」初始化。遇到这些情况就该换:
- 需要不同值(如递增数字)→ 改用
Stream.iterate()或传统循环 - 元素是可变对象且需独立副本 → 必须显式
new每个实例,不能靠nCopies - 初始化后立刻要大量增删 → 考虑
Arrays.asList()+new ArrayList()组合,避免nCopies的额外迭代层 - 在 Kotlin/Scala 等语言中 → 直接用
Array(5) { "x" }或List.fill(5)("x"),语义更自然
最常被忽略的一点:Collections.nCopies() 返回的列表是 Serializable,但它的 ArrayList 包装体不是自动继承该特性——如果涉及网络传输或持久化,得确认序列化行为是否符合预期。










