集合:不可变工厂与 JDK 11 的小补丁
学完你要能自己回答
- List.of / Map.of 是哪个版本?它们和 Arrays.asList、Collections.unmodifiableList 有什么区别?
- 不可变集合到底有多不可变?null、重复元素、浅拷贝分别怎么处理?
- 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.ofEntries | 9 | 可用 | JEP 269,不可变工厂 |
List.copyOf / Set.copyOf / Map.copyOf | 10 | 可用 | 不可变快照,拒绝 null |
Collectors.toUnmodifiableList/Set/Map | 10 | 可用 | 收集为不可变集合 |
Optional.isEmpty | 11 | 可用 | 替代 !isPresent() |
Predicate.not | 11 | 可用 | 否定一个 Predicate |
Collection.toArray(IntFunction) | 11 | 可用 | toArray(String[]::new) |
Stream.toList | 16 | 不可用 | 返回不可变 List |
SequencedCollection / getFirst | 21 | 不可用 | 首尾统一 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/remove | set | null | 源变化是否反映 |
|---|---|---|---|---|
Arrays.asList("a", "b") | UnsupportedOperationException | 允许 | 允许 | 是(同一数组视图) |
Collections.unmodifiableList(src) | UnsupportedOperationException | UnsupportedOperationException | 取决于 src | 是(只读视图) |
List.copyOf(src) | UnsupportedOperationException | UnsupportedOperationException | 拒绝 null | 否(快照) |
List.of("a", "b") | UnsupportedOperationException | UnsupportedOperationException | 拒绝 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 | 零拷贝包装 |
| 高频并发写 | ConcurrentHashMap | CAS + 桶锁,弱一致迭代器 |
| 需要顺序 | 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.of | 9 正式 | JEP 269;不是 JDK 11 新特性。 |
| List.copyOf / Set.copyOf / Map.copyOf | 10 正式 | 不可变快照,拒绝 null。 |
| Collectors.toUnmodifiableList/Set/Map | 10 正式 | 收集结果不可变。 |
| Optional.isEmpty | 11 正式 | 替代 !isPresent()。 |
| Predicate.not | 11 正式 | 否定 Predicate,配合方法引用。 |
| Collection.toArray(IntFunction) | 11 正式 | toArray(String[]::new)。 |
| Stream.toList | 16 正式 | JDK 11 不可用,用 Collectors.toList / toUnmodifiableList。 |
| SequencedCollection / getFirst | 21 正式 | JDK 11 不可用。 |
费曼自检SELF CHECK
先合上页面,用自己的话回答,再展开对照。能讲出因果和反例,才算真正掌握。