collections.fill 是批量赋值引用而非复制对象,所有位置共享同一实例;适用于填充不可变对象或占位符,但填充可变对象会导致数据污染,应避免用于需独立对象的场景。

Collections.fill 是 Java 集合工具类中一个简单但容易误用的方法,它不创建新元素,而是用指定对象覆盖集合中每个位置的引用。理解它的行为关键在于区分“值填充”和“引用覆盖”,尤其在处理可变对象时极易引发意外共享问题。
fill 方法的本质:批量赋值引用,而非复制对象
它对 List(必须是可修改的、有固定大小的)执行遍历,将传入的 同一个对象引用 赋给每个索引位置。这意味着:
- 所有位置指向内存中的同一个对象实例(不是副本)
- 后续通过任一位置修改该对象状态,其他位置看到的也是同一份变化
- 若填入 null,则所有元素被设为 null;若填入 new String("a"),则所有元素都指向这个字符串对象(字符串不可变,所以安全)
正确使用场景:填充不可变对象或占位符
适合用于初始化默认值,前提是被填充的对象本身不会被后续修改:
- 填充 null、Boolean.TRUE、0、"" 等常量或不可变对象
- 预分配固定大小的 List 并用占位符(如 new Object())占位,之后再逐个替换
- 配合 Collections.nCopies 创建不可变副本后转为 ArrayList 再 fill(注意:nCopies 返回的是视图,不能直接 fill)
高危陷阱:填充可变对象导致数据污染
以下代码看似初始化了 3 个独立的 ArrayList,实则全部指向同一个实例:
错误示例:ArrayList<integer> proto = new ArrayList(Arrays.asList(1, 2)); List<arraylist>> list = new ArrayList(Collections.nCopies(3, null)); Collections.fill(list, proto); // 所有元素都引用 proto</arraylist></integer>
此时 list.get(0).add(99) 会导致 list.get(1) 和 list.get(2) 也包含 99 —— 因为它们根本就是同一个对象。
正确做法是用循环分别 new 出新对象,或借助 Stream.generate:
List<arraylist>> safe = Stream.generate(() -> new ArrayList(proto))
.limit(3)
.collect(Collectors.toList());</arraylist>
替代方案建议:按需选择更安全的初始化方式
fill 不适合构造含独立可变对象的集合。推荐方式包括:
- 循环 + new:逻辑清晰,控制力强
- Stream.generate + limit:函数式风格,适合简单构造
- Arrays.asList + Collections.unmodifiableList:适用于固定常量集合
- Guava 的 Lists.newArrayListWithCapacity + 循环添加:兼顾性能与语义明确性
不复杂但容易忽略:fill 只是快捷的引用广播操作,不是对象工厂。用之前先问一句——这个引用被改了,我是否愿意让所有位置一起变?











