← 返回题目列表

Java 怎么创建不可变集合?List.of 和 Collections.unmodifiableList 有什么区别?

中等 第 24 / 30 题 更新于 2026/07/27
不可变集合List.ofunmodifiableList视图

简化版

不可变集合是「创建后不能再增删改的集合」,好处是线程安全(不可变天然安全)、可安全共享、防意外修改。Java 里创建不可变集合有几种:List.of()/Set.of()/Map.of()(Java 9+,推荐)——真正的不可变集合,任何修改都抛 UnsupportedOperationException,且不允许 null 元素;Collections.unmodifiableList(list)——返回原集合的「只读视图」,通过这个视图不能改,但原集合还能改(改了视图也跟着变),所以是「假不可变」(视图不可变,底层可变);List.copyOf(list)(Java 10+)——复制一份真不可变的副本(和原集合脱钩)。核心区别:List.of / copyOf 是真不可变(独立、彻底),unmodifiableList 只是只读视图(原集合可变、会联动)

详细版

三种方式对比

List<String> src = new ArrayList<>(List.of("a", "b"));

// ① List.of(Java 9+):真不可变集合
List<String> immutable = List.of("a", "b", "c");
immutable.add("x");   // ✗ UnsupportedOperationException
// List.of(null);     // ✗ NullPointerException(不允许 null)

// ② Collections.unmodifiableList:只读"视图"(底层还是 src)
List<String> view = Collections.unmodifiableList(src);
view.add("x");        // ✗ 不能通过视图改
src.add("x");         // ✓ 但原集合能改!
System.out.println(view);   // [a, b, x]——视图跟着原集合变了!(假不可变)

// ③ List.copyOf(Java 10+):真不可变的副本(和 src 脱钩)
List<String> copy = List.copyOf(src);
src.add("y");         // 改原集合
System.out.println(copy);   // 不含 y——copy 是独立副本(真不可变)

对比表

方式是否真不可变和原集合的关系null 元素版本
List.of(...)✅ 真不可变无关(直接创建)❌ 不允许Java 9+
Collections.unmodifiableList(src)❌ 只读视图联动(原集合改了视图变)看原集合早期
List.copyOf(src)✅ 真不可变独立副本(脱钩)❌ 不允许Java 10+

⚠️ Collections.unmodifiableList 是「视图」不是「副本」——它只是包了一层「禁止通过我修改」的壳,但底层还是原来那个集合。所以「原集合改了,这个『不可变』视图也会跟着变」(假不可变)。如果你想返回一个「外部改不了、也不受原集合影响」的真不可变集合,要用 List.of(新建)或 List.copyOf(复制副本)。这是「假不可变」最容易踩的坑——以为拿到的是不可变的,结果它悄悄变了。

完整版教学

一、为什么需要不可变集合

不可变集合是「创建后就不能再改(增删改)的集合」,它有几个重要好处:

不可变集合的好处:
  ① 线程安全:不可变对象天生线程安全(没有写操作,多线程读无需同步)
  ② 可安全共享:可以放心地把它传给别人/返回给外部,不怕被改
  ③ 防意外修改:作为常量、配置、方法返回值时,防止被误改
  ④ 可作为可靠的 key:不可变对象的 hashCode 不变,适合做 Map key

对比可变集合的隐患:
  返回一个 ArrayList → 外部可能偷偷 add/remove 它 → 破坏你的内部状态
  用可变集合做 Map key → key 被改后 hashCode 变 → 取不出来

不可变集合的核心价值是「安全」——不可变意味着「状态永远不变」,所以线程安全、可共享、可靠。它是「防御性编程」的重要手段:方法返回集合时返回不可变的(防止外部改你的内部数据)、常量集合用不可变的(防止被误改)。这和 String 不可变、Record 不可变是同一个思想(不可变=安全)。理解「不可变集合线程安全、可安全共享、防意外修改」,就理解了它为什么重要——在「共享、并发、防御」的场景很有用。

二、List.of / Set.of / Map.of:真不可变(Java 9+)

Java 9 引入了 List.of()Set.of()Map.of() 工厂方法,是创建真不可变集合的现代方式:

List<String> list = List.of("a", "b", "c");         // 不可变 List
Set<String> set = Set.of("a", "b");                 // 不可变 Set(不允许重复)
Map<String, Integer> map = Map.of("a", 1, "b", 2);  // 不可变 Map
Map<String, Integer> bigMap = Map.ofEntries(         // 多于 10 个 entry 用 ofEntries
    Map.entry("a", 1), Map.entry("b", 2)
);

// 任何修改都抛异常
list.add("x");   // ✗ UnsupportedOperationException

of 系列的特点:① 真正不可变(任何 add/remove/set 都抛 UnsupportedOperationException);② 不允许 nullList.of(null) 抛 NPE——不可变集合不接受 null,这是有意设计);③ Set.of/Map.of 不允许重复元素/key(重复会抛 IllegalArgumentException);④ 简洁(一行创建)。它是「创建不可变常量集合」的首选——比老式的 Collections.unmodifiableList(Arrays.asList(...)) 简洁得多。理解「List.of/Set.of/Map.of 创建真不可变集合、不允许 null、简洁、Java 9+」,就掌握了现代创建不可变集合的方式。

三、Collections.unmodifiableList:只读视图(假不可变)

Collections.unmodifiableList早期创建「不可变」集合的方式,但它是「只读视图」不是「真不可变」——这是关键的坑:

List<String> src = new ArrayList<>(List.of("a", "b"));
List<String> view = Collections.unmodifiableList(src);   // 只读视图

view.add("x");   // ✗ 不能通过 view 修改(抛 UnsupportedOperationException)
src.add("x");    // ✓ 但可以改原集合 src!
System.out.println(view);   // [a, b, x]——view 跟着 src 变了!

问题的本质:unmodifiableList 只是给原集合包了一层「禁止通过我修改」的壳,但底层引用的还是那个可变的原集合。所以:① 通过 view 不能改(壳挡住了);② 但原集合 src 还能改,改了 view 也跟着变(因为 view 就是 src 的视图)。所以它是「假不可变」——「视图不可变、底层可变」。要让它真正不可变,得保证「没有人持有原集合的引用能改它」(如 Collections.unmodifiableList(new ArrayList<>(src))——先复制一份再包,但这样也繁琐)。理解「unmodifiableList 是只读视图、原集合可变会联动、是假不可变」,就避开了这个高频坑——别以为拿到 unmodifiableList 就万无一失。

四、List.copyOf:真不可变副本(Java 10+)

List.copyOf(Java 10+)解决了 unmodifiableList 的「假不可变」问题——它复制一份真不可变的独立副本

List<String> src = new ArrayList<>(List.of("a", "b"));
List<String> copy = List.copyOf(src);   // 复制一份真不可变副本

src.add("x");                // 改原集合
System.out.println(copy);    // [a, b]——copy 不受影响(独立副本)
copy.add("y");               // ✗ UnsupportedOperationException(真不可变)

List.copyOf 的特点:① 复制独立副本(和原集合脱钩,原集合改了它不变);② 真不可变(副本本身不能改);③ 不允许 null。它是「从一个现有集合,创建一个真不可变副本」的最佳方式——比 Collections.unmodifiableList(new ArrayList<>(src)) 简洁、意图清晰。一个小优化:如果 src 本身已经是不可变集合(如 List.of 创建的),copyOf 会直接返回它(不复制,省内存)。

选择:
  从零创建不可变集合 → List.of("a", "b")
  从现有集合创建真不可变副本 → List.copyOf(src)
  只想要个只读视图(且知道底层会变、就想要联动)→ Collections.unmodifiableList

理解「List.copyOf 复制真不可变独立副本、和原集合脱钩、Java 10+」,就掌握了「从现有集合创建真不可变」的正确方式。

五、真不可变 vs 视图不可变的本质

把三种方式的「不可变性」本质对比清楚,是这道题的核心:

"真不可变"(List.of / List.copyOf):
  集合本身就是不可变的(内部数据不能改)
  没有任何途径能修改它(自己不能改、也不受外部影响)
  → 彻底安全、可放心共享

"视图不可变"(Collections.unmodifiableList):
  只是"禁止通过这个视图修改",但底层集合还可变
  → 如果有人持有底层集合的引用并修改,视图会跟着变
  → "假不可变":看起来不可变,实则底层可变

关键区别:真不可变是"数据本身不可变"、视图不可变是"这个入口不能改但数据可能被别的入口改"

一个类比:真不可变像「刻在石头上的字」(谁也改不了)视图不可变像「戴了手套摸屏幕」(你摸不动,但别人能直接操作屏幕,屏幕内容会变)。所以「视图不可变」提供的安全性是有限的——只有在「确保没人持有底层集合的可变引用」时才真安全。这也是为什么现代 Java 推荐 List.of/copyOf(真不可变)而非 unmodifiableList(视图)。理解「真不可变是数据本身不可变(安全彻底)、视图不可变是入口只读但底层可变(假不可变有隐患)」,就抓住了这道题的精髓。

六、浅不可变的边界

还有一个进阶点——Java 的不可变集合都是「浅不可变(shallow immutable)」

"浅不可变"的含义:
  不可变集合保证"集合结构不可变"(不能增删元素、不能替换元素)
  但"元素对象本身"如果是可变的,元素的内部状态还能改!

List<StringBuilder> list = List.of(new StringBuilder("a"));
list.add(...);              // ✗ 不能增删元素(集合不可变)
list.get(0).append("b");   // ✓ 但能改元素对象的内部状态!(StringBuilder 是可变的)
// list 里的 StringBuilder 现在是 "ab" 了

所以:不可变集合 ≠ 里面的元素也不可变
  → 要彻底不可变,元素本身也得是不可变的(如 String、Integer、Record)

关键认知:不可变集合只保证「集合层面不可变」(不能增删改元素引用),不保证「元素对象本身不可变」。如果元素是可变对象(StringBuilder、可变的自定义类),你还能改元素的内部状态。要「深不可变」,得让元素也是不可变的(用 String、Record 等不可变类型做元素)。这和「final 修饰引用只保证引用不变、对象内容可变」是同一个道理(前面 final 题讲过)。理解「不可变集合是浅不可变、元素对象本身若可变仍能改、要深不可变需元素也不可变」,就补齐了不可变集合的完整认知——别以为不可变集合里的一切都冻结了。

记忆钩子:「不可变集合线程安全/可共享/防误改;List.of/Set.of/Map.of(Java 9+)是真不可变(改抛 UnsupportedOperationException、不允许 null、简洁);Collections.unmodifiableList 是只读视图=假不可变(原集合改了它联动变、只挡住通过视图改);List.copyOf(Java 10+)是真不可变独立副本(和原集合脱钩);都是浅不可变(集合结构不可变但可变元素的内部状态仍能改,要深不可变需元素也不可变)」

七、常见误区与追问

  • 误区:Collections.unmodifiableList 是真不可变。 是只读视图(假不可变)——只挡住「通过这个视图修改」,底层原集合还能改,改了视图会联动变;真不可变用 List.of 或 List.copyOf。
  • 误区:List.of 可以放 null。 不允许——List.of(null) 抛 NPE;不可变集合有意不接受 null(Set.of/Map.of 还不允许重复元素/key)。
  • 误区:不可变集合里的元素也都不可变。 是浅不可变——集合结构不可变(不能增删改元素引用),但如果元素是可变对象(StringBuilder),元素的内部状态还能改;要深不可变需元素本身也不可变。
  • 误区:unmodifiableList 和 copyOf 一样。 不同——unmodifiableList 是原集合的只读视图(联动、假不可变),copyOf 是独立的真不可变副本(脱钩、真不可变)。
  • 追问:List.of 和 Collections.unmodifiableList 的区别? List.of 直接创建真不可变集合(独立、不允许 null、Java 9+);unmodifiableList 返回原集合的只读视图(原集合改了它联动变、是假不可变),要真不可变副本用 List.copyOf。
  • 追问:怎么从一个现有集合创建真不可变集合? 用 List.copyOf(src)(Java 10+,复制独立的真不可变副本、和原集合脱钩);别用 Collections.unmodifiableList(那是只读视图、会联动)。
  • 追问:不可变集合是深不可变还是浅不可变? 浅不可变——保证集合结构不可变(不能增删改元素引用),但不保证元素对象本身不可变(可变元素的内部状态仍能改);要深不可变需元素也用不可变类型。

八、加强记忆

不可变集合是「创建后不能再增删改」的集合,好处是线程安全、可安全共享、防意外修改(不可变=安全,同 String/Record)。三种创建方式的关键区别在「真不可变 vs 视图不可变」:List.of/Set.of/Map.of(Java 9+)真不可变(任何修改抛 UnsupportedOperationException不允许 null、简洁、直接创建、独立);Collections.unmodifiableList(src)只读视图=假不可变(只挡住「通过这个视图改」,底层原集合还能改、改了视图联动变——这是高频坑);List.copyOf(src)(Java 10+)真不可变的独立副本(复制一份、和原集合脱钩、原集合改了它不变)。选择:从零创建用 List.of、从现有集合创建真不可变副本用 List.copyOf、只要只读视图(接受联动)才用 unmodifiableList。另外所有不可变集合都是浅不可变——集合结构不可变(不能增删改元素引用),但可变元素对象的内部状态仍能改(要深不可变需元素本身也是不可变类型如 String/Record)。一句话「List.of/copyOf 真不可变(独立、不允许 null)、unmodifiableList 是只读视图假不可变(原集合改了联动)、都是浅不可变(可变元素内部仍可改)」。