"unmodifiableList 返回的是不可变集合"这句话只对了一半——它确实不允许你通过这个返回值去修改内容,但它拦不住原始集合被改,而且改了之后"不可变"的那份会真实同步跟着变。
上一篇:Arrays.asList() 真实是个固定大小的视图:改它真的会改到原始数组
Collections.unmodifiableList(original) 返回的是一个包装类,内部持有的就是 original 这个引用本身,每次访问都真实转发到 original 上。
UnmodifiableViewDemo.java ArrayList<String> original = new ArrayList<>(List.of("a", "b", "c")); List<String> unmodifiable = Collections.unmodifiableList(original); original.add("d"); // 只改了 original,没碰 unmodifiable
unmodifiable = [a, b, c]
original.add("d") 之后:
original = [a, b, c, d]
unmodifiable = [a, b, c, d]("不可变"视图是否也真的变了?)
original.remove("a") 之后:
original = [b, c, d]
unmodifiable = [b, c, d]
真实结果很直接:代码里从来没有对 unmodifiable 这个变量做过任何操作,但它每次被打印出来的内容都真实和 original 保持同步——因为它本来就不是一份独立的数据,只是一个"这里不允许你改"的访问权限限制,包在同一份底层数据外面。
unmodifiableList 真实起作用的地方,是拦截"通过这个引用发起的修改"。
unmodifiable.add("z") 真实抛出 UnsupportedOperationException
这一点符合预期,也是这个方法名字的字面含义所在——但真实验证下来,它保护的范围比很多人以为的要窄:它只保证"你没法通过这个 unmodifiable 变量修改内容",完全不保证"这份数据不会变"。如果调用方以为拿到一个 unmodifiableList 就等于拿到了一份安全的、不会被意外修改的快照,而背后还有别的代码持有 original 的引用并继续修改它,这个"不可变"的假设就是错的。
正确做法是先用拷贝构造函数复制一份独立数据,再包装成不可变视图。
先拷贝(new ArrayList<>(original))再包装成 unmodifiableList 之后,再改 original: original = [b, c, d, e] realSnapshot = [b, c, d](这次才真的没受影响,因为拷贝出了独立的底层数据)这次真实做到了完全隔离——realSnapshot 内部持有的是一份独立分配的数组,和 original 之后无论怎么增删,两者都真实互不影响
每一步的内容都是真实运行这段代码之后的状态。