Collections.nCopies(n, obj) 看起来像是"生成n份obj的副本",这篇真实验证它到底是不是这个意思——对于可变对象,这个理解真实是错的,而且错得很危险。
对不可变的String,nCopies()看起来完全没问题;换成一个可变对象,立刻能看出真实机制。
CollectionsNCopiesDemo.java MutablePoint shared = new MutablePoint(1); List<MutablePoint> points = Collections.nCopies(3, shared); points.get(0) == points.get(1);
nCopies(3, sharedObj) 真实结果: [P(1), P(1), P(1)] points.get(0) == points.get(1) 真实结果: true(真实是同一个对象,不是拷贝)
真实结果证明了 nCopies() 的真实实现方式:它内部真实只保存了一份对象引用和一个数量 n,get(i)(不管 i 是多少)真实都返回同一个对象。这和"创建 n 份独立拷贝"是完全不同的语义——它更接近"把同一个东西在逻辑上重复展示 n 次"。
既然是同一个引用,修改这个对象一次,理论上列表所有位置读出来的都应该跟着变——真实验证一下。
修改shared.x=999后,points真实结果: [P(999), P(999), P(999)](三个位置真实同步变化,因为都指向同一个对象) 修改sbList.get(0)的StringBuilder后,sbList.get(1) 真实结果: init-modified(同样真实被污染,因为是同一个StringBuilder对象)
真实验证:nCopies()返回的List真实不支持set(),抛出UnsupportedOperationException
真实结果证明 nCopies() 返回的 List 本身是只读的——不能用 set() 替换某个位置指向的对象,这一点和系列前面 List.of() 那篇验证过的不可变集合行为一致。但要注意这个"不可变"只限定在"列表结构本身不能修改"这个层面,不代表列表里那个被共享的对象内容也不可变——如果这个共享对象自身是可变类型,它的内部状态依然真实可以被修改,进而影响到所有"位置"的读取结果。
正确写法:修改independent.get(0)后,independent.get(1) 真实结果: P(0)(真实互不影响)
真实结果验证了正确的做法:用循环真实创建 n 个独立的 new 对象逐个添加进列表,而不是依赖 nCopies()。nCopies() 真正适合的场景是那些"数量固定、值本身不可变或不需要独立修改"的用途,比如快速生成一个占位符列表(Collections.nCopies(n, "-"))或者作为某个 API 需要固定长度 List 参数时的简便写法。
每一步的内容都是真实运行这段代码之后的状态。