JDK JDK 学习手册8 → 27 · JDK 专用学习站
QUICKREF · COLLECTIONS · 9 / 10 / 11

集合:不可变工厂与 JDK 11 的小补丁

JDK 11 · 速查主题 04 / 06 · 预计 24 分钟
核心24 分钟JEP 269
一句话List.of / Set.of / Map.of 是 JDK 9 的工厂方法,List.copyOf 是 JDK 10 的不可变拷贝;JDK 11 只补了 Optional.isEmpty、Predicate.not 和 Collection.toArray(IntFunction) 等小方法。把版本拆清楚,比背 API 更重要。

学完你要能自己回答

  1. List.of / Map.of 是哪个版本?它们和 Arrays.asList、Collections.unmodifiableList 有什么区别?
  2. 不可变集合到底有多不可变?null、重复元素、浅拷贝分别怎么处理?
  3. JDK 11 给集合和函数式补了哪些小方法?为什么 Stream.toList 不能用?

版本地图:9、10、11 各加了什么01

集合 API 的版本归属很容易混。JDK 9 引入了不可变工厂,JDK 10 引入了 copyOf 和不可变 Collector,JDK 11 只加了几个便利方法。

API最早版本JDK 11说明
List.of / Set.of / Map.of / Map.ofEntries9可用JEP 269,不可变工厂
List.copyOf / Set.copyOf / Map.copyOf10可用不可变快照,拒绝 null
Collectors.toUnmodifiableList/Set/Map10可用收集为不可变集合
Optional.isEmpty11可用替代 !isPresent()
Predicate.not11可用否定一个 Predicate
Collection.toArray(IntFunction)11可用toArray(String[]::new)
Stream.toList16不可用返回不可变 List
SequencedCollection / getFirst21不可用首尾统一 API

下面用 --release 实测边界:List.copyOf 在 release 9 不存在,Optional.isEmpty 在 release 10 不存在,release 11 才全部通过。

import java.util.*;

public class CollApi {
    public static void main(String[] args) {
        List<String> copy = List.copyOf(List.of("a"));
        System.out.println(Optional.empty().isEmpty() + " " + copy);
    }
}
javac --release 9  : cannot find symbol: method copyOf(List<String>)
javac --release 10 : cannot find symbol: method isEmpty()
javac --release 11 : 编译通过

这段程序本身的运行输出是 true [a];上面三行记录的是三个 --release 下的编译结果,不是运行时输出。

List.of / Map.of:短小、不可变、拒绝 null02

工厂方法返回的是高度优化的不可变集合。它们不接受 null,重复元素/键会抛异常;不要把它当成「初始化可变集合」的快捷方式。

import java.util.*;

public class FactoryDemo {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b");
        System.out.println("list=" + list);
        try { list.add("c"); } catch (UnsupportedOperationException e) { System.out.println("list.add=UOE"); }
        try { list.set(0, "z"); } catch (UnsupportedOperationException e) { System.out.println("list.set=UOE"); }
        try { List.of("a", null); } catch (NullPointerException e) { System.out.println("list.null=NPE"); }

        try { Set.of("a", "a"); } catch (IllegalArgumentException e) { System.out.println("set.dup=IAE"); }
        try { Map.of("k", 1, "k", 2); } catch (IllegalArgumentException e) { System.out.println("map.dup=IAE"); }
        try { Map.of("k", null); } catch (NullPointerException e) { System.out.println("map.null=NPE"); }
    }
}
list=[a, b]
list.add=UOE
list.set=UOE
list.null=NPE
set.dup=IAE
map.dup=IAE
map.null=NPE
  • List.of 的 add/set/remove 都抛 UnsupportedOperationException;它不是固定大小的 Arrays.asList。
  • Set.of 的重复元素在运行时抛 IllegalArgumentException,不是编译错误。
  • Map.of 的重复键同样运行时抛 IllegalArgumentException;null 键或值抛 NullPointerException。
  • Map.of 最多 10 对键值;更多时用 Map.ofEntries(Map.entry(...), ...)。
  • Map.of 的迭代顺序未指定,不要依赖它;需要顺序时用 LinkedHashMap 或 TreeMap。
工厂方法适合常量表、返回值和小型配置。需要后续增删时,用 new ArrayList<>(List.of(...)) 或 new HashMap<>(Map.of(...)) 显式复制。

不可变视图、固定大小、快照:三种「不可变」03

Arrays.asList 是固定大小,Collections.unmodifiableList 是只读视图,List.copyOf 是不可变快照。三者的行为差异,比「都不能 add」这句话重要得多。

方式add/removesetnull源变化是否反映
Arrays.asList("a", "b")UnsupportedOperationException允许允许是(同一数组视图)
Collections.unmodifiableList(src)UnsupportedOperationExceptionUnsupportedOperationException取决于 src是(只读视图)
List.copyOf(src)UnsupportedOperationExceptionUnsupportedOperationException拒绝 null否(快照)
List.of("a", "b")UnsupportedOperationExceptionUnsupportedOperationException拒绝 null不适用
import java.util.*;

public class ViewDemo {
    public static void main(String[] args) {
        List<String> src = new ArrayList<>(List.of("a"));
        List<String> view = Collections.unmodifiableList(src);
        List<String> copy = List.copyOf(src);

        src.add("b");
        System.out.println("view=" + view);
        System.out.println("copy=" + copy);
        try { view.add("c"); } catch (UnsupportedOperationException e) { System.out.println("view.add=UOE"); }
    }
}
view=[a, b]
copy=[a]
view.add=UOE

List.copyOf 会创建快照,之后源集合的变化不影响它;Collections.unmodifiableList 只是包了一层,源集合变化会立即反映。需要稳定快照时用 copyOf。

不可变集合只保证结构不可变,不保证元素对象不可变。如果元素是可变对象,修改元素内部状态仍然会反映到集合里。

List<StringBuilder> list = List.of(new StringBuilder("a"));
list.get(0).append("b");
System.out.println(list); // [ab]
List.copyOf 对已经是不可变 List 的输入可能直接返回同一实例,这是实现优化;不要依赖对象身份,只依赖行为契约。

JDK 11 的小补丁:isEmpty、not、toArray04

JDK 11 没有大改集合框架,但补了几个很常用的小方法。它们经常和 Stream、Optional 一起出现。

import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StreamPatch {
    public static void main(String[] args) {
        System.out.println("optional.isEmpty=" + Optional.empty().isEmpty());
        System.out.println("predicate.not=" + Stream.of("", "a", " ")
                .filter(Predicate.not(String::isBlank))
                .collect(Collectors.toList()));

        String[] arr = List.of("x", "y").toArray(String[]::new);
        System.out.println("toArray=" + Arrays.toString(arr));

        List<Integer> mutable = Stream.of(1, 2).collect(Collectors.toList());
        mutable.add(3);
        System.out.println("collector=" + mutable);

        List<Integer> unmod = Stream.of(1, 2).collect(Collectors.toUnmodifiableList());
        try { unmod.add(3); } catch (UnsupportedOperationException e) { System.out.println("unmod.add=UOE"); }
    }
}
optional.isEmpty=true
predicate.not=[a]
toArray=[x, y]
collector=[1, 2, 3]
unmod.add=UOE

Collectors.toList() 返回可变 List(实现通常是 ArrayList),Collectors.toUnmodifiableList() 返回不可变 List。JDK 11 没有 Stream.toList(),后者是 JDK 16 才加入的。

System.out.println(Stream.of(1, 2).toList());
error: cannot find symbol
  symbol:   method toList()
  location: interface Stream<Integer>

选型:可变、不可变、并发05

不可变集合不是默认答案,而是表达「这个集合不应该被修改」的工具。需要频繁增删时,可变集合更合适;需要并发写入时,ConcurrentHashMap 比同步包装更细粒度。

场景优先选择原因
常量列表 / 返回值List.of / List.copyOf不可变、拒绝 null、语义清楚
需要后续增删new ArrayList<>() / new HashMap<>()可变,API 完整
固定大小的数组视图Arrays.asList可 set,不可 add/remove
只读视图,跟随源变化Collections.unmodifiableList零拷贝包装
高频并发写ConcurrentHashMapCAS + 桶锁,弱一致迭代器
需要顺序LinkedHashMap / TreeMap插入顺序 / 排序
枚举键EnumMap / EnumSet内存紧凑、速度快
ConcurrentHashMap 不允许 null 键和 null 值;computeIfAbsent 的映射函数里不要再修改同一个 map,否则可能死锁或抛 IllegalStateException。

JDK 11 还没有 SequencedCollection。getFirst()、getLast()、reversed() 这些方法在 JDK 21 才出现;JDK 11 里请继续用 get(0)、get(size - 1) 或 Deque 的 peekFirst()。

版本边界BOUNDARY

这张表比 API 本身更值得记。写代码时先确认你的运行时版本。

特性 / API状态说明
List.of / Set.of / Map.of9 正式JEP 269;不是 JDK 11 新特性。
List.copyOf / Set.copyOf / Map.copyOf10 正式不可变快照,拒绝 null。
Collectors.toUnmodifiableList/Set/Map10 正式收集结果不可变。
Optional.isEmpty11 正式替代 !isPresent()。
Predicate.not11 正式否定 Predicate,配合方法引用。
Collection.toArray(IntFunction)11 正式toArray(String[]::new)。
Stream.toList16 正式JDK 11 不可用,用 Collectors.toList / toUnmodifiableList。
SequencedCollection / getFirst21 正式JDK 11 不可用。

费曼自检SELF CHECK

先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。

01. List.of / Map.of 是 JDK 11 的新特性吗?
不是。它们来自 JDK 9 的 JEP 269;JDK 10 增加了 copyOf 和不可变 Collector;JDK 11 只补了 Optional.isEmpty、Predicate.not、toArray(IntFunction) 等小方法。
02. Arrays.asList、Collections.unmodifiableList、List.copyOf 有什么区别?
Arrays.asList 是固定大小视图,允许 set、不允许 add/remove;unmodifiableList 是只读视图,源集合变化会反映;copyOf 是不可变快照,拒绝 null,源集合之后的变化不影响它。
03. List.of 拒绝 null 意味着什么?
任何 null 元素都会在构造时抛 NullPointerException。需要允许 null 时不能用 List.of;应先过滤,或使用 ArrayList 等可变集合。
04. 为什么 JDK 11 不能写 Stream.toList()?
Stream.toList 是 JDK 16 才加入的。JDK 11 应使用 Collectors.toList()(可变)或 Collectors.toUnmodifiableList()(不可变)。
学习进度 记录保存在本机浏览器 · 不上传

JDK 学习手册 · JDK / Java 专用学习站 · 纯静态 · 双击 index.html 即可使用

本站专注 JDK 版本演进与 Java 教学,非 Oracle / OpenJDK 官方站点。版本数据来自 OpenJDK 官方 Features 表;课程示例已在对应本机 JDK 实测。构建日期 2026-11-12。