Collections.unmodifiableList() 返回不可修改视图,仅防止集合结构变更,不阻止元素自身修改;需配合副本创建、私有封装及不可变元素类型才能真正防篡改。

用 Collections.unmodifiableList() 包装核心数组,本质是**包装已有 List(如由数组构造的 ArrayList),而非直接包装原始数组本身**。它返回一个不可修改的视图,任何修改操作(如 add、set、clear)都会抛出 UnsupportedOperationException,从而在运行时拦截非法写入。
确保被包装的是“真实可变底层数组”的 List 视图
常见误区是直接对原始数组调用该方法——但 Collections.unmodifiableList() 只接受 List 参数。必须先将数组转为 List,且需注意:Arrays.asList(array) 返回的是**固定大小、直接引用原数组元素**的 List,修改它会同步改原数组;而 unmodifiableList 包装它后,虽不能增删,仍可通过 set(i, x) 修改元素值(因为底层仍是原数组)。所以真正防篡改,要避免暴露可写视图:
- 若数组内容本身不应被修改,优先使用
Arrays.asList(array).toArray()创建副本,再包装:List<string> safeView = Collections.unmodifiableList(new ArrayList(Arrays.asList(coreArray)));</string> - 更稳妥的做法是:初始化时就用不可变结构(如
ImmutableList.copyOf(coreArray),来自 Guava),或封装成私有字段+只读 getter。
只暴露 unmodifiableList,绝不返回原始 List 或数组引用
即使包装了,如果业务代码中还持有原始 ArrayList 或数组变量,依然可能被误改。关键在于“封装边界”:
- 将核心数组声明为 private final,不提供 public setter;
- getter 方法返回
Collections.unmodifiableList(...)结果,而不是原始 list; - 避免在方法内返回内部 list 的中间计算结果(如
list.subList(0, 5)),除非也立即包装。
理解它的局限性:浅不可变,不防内容对象自身变化
unmodifiableList 保证的是集合结构不可变(不能增删改索引位置),但**不阻止列表中对象自身的状态变更**。例如:
- 若列表存的是
Person对象,虽然不能list.set(0, new Person()),但可以list.get(0).setName("xxx"); - 要彻底防篡改,需确保元素类型本身不可变(如用
String、LocalDate),或自定义类加final字段 + 无 setter。
替代方案对比:何时用其他方式更合适
单靠 unmodifiableList 不是万能解:
- 需要深度不可变(含嵌套对象)→ 用
ImmutableList(Guava)或List.copyOf()(Java 10+,仅适用于不可变元素); - 频繁创建只读视图且性能敏感 → 考虑自定义轻量 wrapper 类,避免每次新建 list 对象;
- 想从源头杜绝修改 → 初始化后立即将数组设为
final并只通过封装方法访问,比后期包装更可靠。

















