在Java中如何使用Collections.unmodifiableList创建不可变集合_Java集合不可变解析

Collections.unmodifiableList并非真正不可变,它仅通过强引用代理原列表并拦截写操作,原列表修改会实时反映;真正不可变需切断原始引用或使用List.of()等隔离实现。

直接说结论:Collections.unmodifiableList 不创建新集合,只是给原 List 套一层“只读外壳”,原集合一旦被修改,不可变视图会立刻反映变化(甚至抛出 UnsupportedOperationException),它**不是真正意义上的不可变集合**。

为什么调用 unmodifiableList 后还能看到原列表的修改?

因为 unmodifiableList 返回的是一个内部静态类 UnmodifiableRandomAccessList(或 UnmodifiableList),它持有一个对原始 List 的**强引用**,所有读操作(如 get()size())都委托给底层列表执行。它只拦截写操作(如 add()set()),并统一抛出 UnsupportedOperationException

这意味着:

  • 如果原始列表是 new ArrayList(),且你在 unmodifiableList 创建后继续往里面 add(),那么不可变视图的 size()get(i) 会返回新内容
  • 如果你把原始列表传给了其他代码并被意外修改,不可变视图将“被动同步”这些变更——这不是 bug,是设计使然
  • 它不阻止并发修改,多线程下仍可能触发 ConcurrentModificationException

如何避免“假不可变”陷阱?

关键在于:**切断对原始可变列表的引用**。否则,只要还有人能拿到原列表,unmodifiableList 就形同虚设。

推荐做法:

  • 立即用新变量接收结果,并将原始引用置为 null(或作用域结束)
  • 更稳妥的方式是先复制再封装:
    List mutable = new ArrayList<>(Arrays.asList("a", "b"));
    List immutable = Collections.unmodifiableList(new ArrayList<>(mutable));

  • 如果数据来自外部(比如方法参数),别直接包装,先做防御性拷贝:
    public void process(List input) {
        List safeCopy = new ArrayList<>(input);
        List readOnly = Collections.unmodifiableList(safeCopy);
        // 后续只使用 readOnly,丢弃 input 和 safeCopy 引用
    }

Java 9+ 替代方案:用 List.of() 更安全

List.of()(以及 Set.of()Map.of())才是真正的不可变集合实现:它们不持有外部引用、不允许 null、内部结构不可修改、且序列化友好。它的底层是私有不可变数组,没有“外壳代理”逻辑。

注意差异:

  • Collections.unmodifiableList 允许 null 元素;List.of() 不允许,遇到 null 直接抛 NullPointerException
  • List.of() 在空集合或单元素时有专门优化,内存更紧凑
  • List.of()Serializable 且实现了 equals/hashCode;而 unmodifiableListequals 行为依赖底层列表实现
  • 如果必须支持 null 或需要动态构造(比如从流中收集),可用 ImmutableList.copyOf(list)(Guava)或 Lists.immutableListOf(...)

真正不可变的关键不在“是否报错”,而在“是否隔离源头”。哪怕用了 unmodifiableList,只要原始引用还活着,就不是终点。