集合工厂方法与增强(JDK 9 / 10 / 11)
飞翔科技的代码评审会上,大翔翻到一段代码,脸色沉了下来。
List<String> ROLES = Arrays.asList("ADMIN", "USER", "GUEST");"小崔,你知道
Arrays.asList()返回的 List 有什么问题吗?"小崔挠头:"它...不可变?"
"错。" 白歌在投影仪上打开 JDK 源码,"
Arrays.asList返回的是固定大小的 List。你不能 add,不能 remove——但你能 set 修改元素。这不是真正的不可变集合。"孔蓝小声问:"那有没有真正不可变的集合?"
白歌笑了:"JDK 9 的
List.of()。一行代码,绝对不可变,元素不能为 null,线程安全。来,我给你们演示。"
定义表
| 方法 | 版本 | 功能 | 特性 |
|---|---|---|---|
List.of(E...) | JDK 9 | 创建不可变 List | 不可修改、不接受 null、线程安全、内存紧凑 |
Set.of(E...) | JDK 9 | 创建不可变 Set | 不可修改、不接受 null、不重复自动抛异常 |
Map.of(K,V,...) | JDK 9 | 创建不可变 Map(≤10对) | 交替传入 key, value,最多 10 对 |
Map.ofEntries(Entry...) | JDK 9 | 创建不可变 Map(任意数量) | 使用 Map.entry(k, v) 构建 |
List.copyOf(Collection) | JDK 10 | 复制为不可变 List | 若源已是不可变集合,直接返回(零拷贝) |
Set.copyOf(Collection) | JDK 10 | 复制为不可变 Set | 同上 |
Map.copyOf(Map) | JDK 10 | 复制为不可变 Map | 同上 |
Collection.toArray(IntFunction) | JDK 11 | 集合转数组 | 比 .toArray(new T[0]) 更简洁且性能更好 |
Mermaid 流程图:集合创建方式选择决策树
List.of / Set.of / Map.of — 不可变集合工厂(JDK 9)
3.1 基础用法对比
import java.util.*;
public class CollectionFactoryDemo {
public static void main(String[] args) {
// ========== List ==========
// JDK 8 及之前:啰嗦的"不可变" List
List<String> jdk8List = Collections.unmodifiableList(
new ArrayList<>(Arrays.asList("ADMIN", "USER", "GUEST"))
);
// JDK 9:一行搞定
List<String> roles = List.of("ADMIN", "USER", "GUEST");
System.out.println("角色列表: " + roles);
// ========== Set ==========
// JDK 8:需要先建 List 再转 Set
Set<String> jdk8Set = new HashSet<>(Arrays.asList("Nginx", "Redis", "MySQL"));
// JDK 9:直接创建
Set<String> services = Set.of("Nginx", "Redis", "MySQL");
System.out.println("服务集合: " + services);
// ========== Map ==========
// JDK 8:匿名内部类或逐条 put
Map<String, Integer> jdk8Map = new HashMap<>();
jdk8Map.put("技术部", 25);
jdk8Map.put("市场部", 8);
jdk8Map.put("运维部", 5);
Map<String, Integer> unmodifiableMap = Collections.unmodifiableMap(jdk8Map);
// JDK 9:Map.of(key1, val1, key2, val2, ...)
Map<String, Integer> deptSize = Map.of(
"技术部", 25,
"市场部", 8,
"运维部", 5
);
System.out.println("部门人数: " + deptSize);
}
}
输出:
角色列表: [ADMIN, USER, GUEST]
服务集合: [Nginx, Redis, MySQL]
部门人数: {运维部=5, 市场部=8, 技术部=25}
3.2 Map.of 的限制与 Map.ofEntries
import java.util.Map;
import static java.util.Map.entry;
public class MapOfEntriesDemo {
public static void main(String[] args) {
// Map.of(K,V,...) 最多支持 10 对 key-value
// 超过 10 对就需要 Map.ofEntries()
// === 飞翔科技部门配置(超过 10 对) ===
Map<String, String> deptConfig = Map.ofEntries(
entry("技术部", "大翔"),
entry("技术部-后端", "白歌"),
entry("技术部-前端", "黄俪"),
entry("技术部-测试", "孔蓝"),
entry("市场部", "Frank"),
entry("市场部-品牌", "朱璐"),
entry("运维部", "李眉"),
entry("运维部-安全", "安全组"),
entry("人事部", "王姐"),
entry("财务部", "陈会计"),
entry("法务部", "法律顾问")
);
System.out.println("部门配置数量: " + deptConfig.size());
deptConfig.forEach((dept, lead) ->
System.out.printf(" %-12s → %s%n", dept, lead)
);
}
}
输出:
部门配置数量: 11
技术部 → 大翔
技术部-后端 → 白歌
技术部-前端 → 黄俪
技术部-测试 → 孔蓝
市场部 → Frank
市场部-品牌 → 朱璐
运维部 → 李眉
运维部-安全 → 安全组
人事部 → 王姐
财务部 → 陈会计
法务部 → 法律顾问
3.3 不可变集合的保护机制
import java.util.List;
import java.util.Set;
import java.util.Map;
public class ImmutableProtectionDemo {
public static void main(String[] args) {
List<String> roles = List.of("ADMIN", "USER");
Set<String> services = Set.of("MySQL");
Map<String, Integer> config = Map.of("timeout", 30);
// === 修改操作全部抛异常 ===
try { roles.add("GUEST"); }
catch (UnsupportedOperationException e) {
System.out.println("❌ List.add() → " + e.getClass().getSimpleName());
}
try { roles.set(0, "SUPER_ADMIN"); }
catch (UnsupportedOperationException e) {
System.out.println("❌ List.set() → " + e.getClass().getSimpleName());
}
try { services.add("Redis"); }
catch (UnsupportedOperationException e) {
System.out.println("❌ Set.add() → " + e.getClass().getSimpleName());
}
try { config.put("retry", 3); }
catch (UnsupportedOperationException e) {
System.out.println("❌ Map.put() → " + e.getClass().getSimpleName());
}
// === null 元素在创建时就拒绝 ===
try {
List.of("A", null, "B");
} catch (NullPointerException e) {
System.out.println("❌ List.of(null) → " + e.getClass().getSimpleName());
}
// === Set 不能有重复元素 ===
try {
Set.of("A", "B", "A");
} catch (IllegalArgumentException e) {
System.out.println("❌ Set.of(重复) → " + e.getClass().getSimpleName());
}
// === Map 不能有重复 key ===
try {
Map.of("key", 1, "key", 2);
} catch (IllegalArgumentException e) {
System.out.println("❌ Map.of(重复key) → " + e.getClass().getSimpleName());
}
}
}
输出:
❌ List.add() → UnsupportedOperationException
❌ List.set() → UnsupportedOperationException
❌ Set.add() → UnsupportedOperationException
❌ Map.put() → UnsupportedOperationException
❌ List.of(null) → NullPointerException
❌ Set.of(重复) → IllegalArgumentException
❌ Map.of(重复key) → IllegalArgumentException
copyOf — 智能复制为不可变集合(JDK 10)
import java.util.*;
public class CopyOfDemo {
public static void main(String[] args) {
// === 场景一:从可变集合创建不可变副本 ===
List<String> mutable = new ArrayList<>();
mutable.add("大翔");
mutable.add("白歌");
mutable.add("小崔");
// JDK 10: copyOf 生成独立快照
List<String> snapshot = List.copyOf(mutable);
System.out.println("快照: " + snapshot);
// 修改原集合不影响快照
mutable.add("孔蓝");
System.out.println("原集合(修改后): " + mutable);
System.out.println("快照(不受影响): " + snapshot);
// === 场景二:copyOf 的零拷贝优化 ===
List<String> immutable = List.of("A", "B", "C");
List<String> copied = List.copyOf(immutable);
// 源已经是不可变集合,copyOf 直接返回同一个对象(零拷贝)
System.out.println("\n同一对象? " + (immutable == copied)); // true
// === 场景三:Set.copyOf ===
Set<Integer> nums = new HashSet<>();
nums.add(1);
nums.add(2);
nums.add(3);
Set<Integer> frozen = Set.copyOf(nums);
nums.add(4);
System.out.println("\n原Set: " + nums); // [1, 2, 3, 4]
System.out.println("快照: " + frozen); // [1, 2, 3]
}
}
输出:
快照: [大翔, 白歌, 小崔]
原集合(修改后): [大翔, 白歌, 小崔, 孔蓝]
快照(不受影响): [大翔, 白歌, 小崔]
同一对象? true
原Set: [1, 2, 3, 4]
快照: [1, 2, 3]
toArray(IntFunction) — 优雅转数组(JDK 11)
import java.util.List;
import java.util.Set;
public class ToArrayDemo {
public static void main(String[] args) {
List<String> employees = List.of("大翔", "白歌", "小崔", "孔蓝");
// --- JDK 8 写法一:toArray(new T[0]) ---
String[] arr1 = employees.toArray(new String[0]);
System.out.println("JDK8方式1: " + java.util.Arrays.toString(arr1));
// --- JDK 8 写法二:toArray(new T[size]) ---
String[] arr2 = employees.toArray(new String[employees.size()]);
// --- JDK 11 写法:toArray(IntFunction) ---
String[] arr3 = employees.toArray(String[]::new);
System.out.println("JDK11方式: " + java.util.Arrays.toString(arr3));
// === 实战:飞翔科技员工转数组并排序 ===
var deptEmployees = Set.of("大翔", "白歌", "小崔", "孔蓝", "Frank", "黄俪", "李眉");
// JDK 11 链式操作:转数组 → 排序 → 打印
String[] sorted = deptEmployees.stream()
.sorted()
.toArray(String[]::new);
System.out.println("\n按字母排序:");
for (int i = 0; i < sorted.length; i++) {
System.out.printf(" %d. %s%n", i + 1, sorted[i]);
}
}
}
输出:
JDK8方式1: [大翔, 白歌, 小崔, 孔蓝]
JDK11方式: [大翔, 白歌, 小崔, 孔蓝]
按字母排序:
1. Frank
2. 大翔
3. 小崔
4. 孔蓝
5. 李眉
6. 白歌
7. 黄俪
易错场景
6.1 List.of 返回的集合与 Arrays.asList 的行为差异
import java.util.Arrays;
import java.util.List;
public class ListOfVsAsList {
public static void main(String[] args) {
// Arrays.asList:可 set,不可 add/remove
List<String> asList = Arrays.asList("A", "B", "C");
asList.set(1, "X"); // ✅ 可以
System.out.println("Arrays.asList set后: " + asList);
// List.of:完全不可变
List<String> listOf = List.of("A", "B", "C");
try {
listOf.set(1, "X"); // ❌ 抛异常
} catch (UnsupportedOperationException e) {
System.out.println("List.of set → UnsupportedOperationException");
}
// Arrays.asList:接受 null 元素
List<String> withNull = Arrays.asList("A", null, "C");
System.out.println("Arrays.asList 含null: " + withNull);
// List.of:拒绝 null 元素
try {
List<String> noNull = List.of("A", null, "C");
} catch (NullPointerException e) {
System.out.println("List.of 含null → NullPointerException");
}
}
}
6.2 不可变集合与序列化
import java.util.List;
public class ImmutableSerialization {
public static void main(String[] args) {
// List.of / Set.of / Map.of 返回的集合实现了 Serializable
List<String> roles = List.of("ADMIN", "USER", "GUEST");
boolean serializable = roles instanceof java.io.Serializable;
System.out.println("List.of 返回的是否可序列化: " + serializable);
// 但这与 Collections.unmodifiableList 不同
// unmodifiableList 是"视图"包装,底层集合改变它也跟着变
// List.of 是独立数据副本,与源无关
System.out.println("集合类型: " + roles.getClass().getName());
}
}
面试考点
问题一:"List.of() 和 Arrays.asList() 的区别?"
主要有四点区别。第一,
List.of()返回完全不可变的 List(add、remove、set 全都抛异常),Arrays.asList()返回固定大小的 List(可 set,不可 add/remove)。第二,List.of()不接受 null 元素,Arrays.asList()接受。第三,List.of()返回的集合不依赖数组,是独立数据;Arrays.asList()是数组的视图,数组变了 List 也变。第四,List.of()的空间效率更高(专门优化的内部实现类)。
问题二:"copyOf() 和包装 unmodifiableXxx 的区别?"
copyOf在源已是不可变集合时返回同一个引用(零拷贝优化);unmodifiableXxx总是创建新包装对象。copyOf创建时已做快照,后续源集合变化不影响副本;unmodifiableXxx是视图,源集合变了视图也变。
问题三:"collection.toArray(String[]::new) 和 collection.toArray(new String[0]) 哪个更好?"
JDK 11 的
toArray(String[]::new)更好。它更简洁,且 JVM 会对其做 intrinsic 优化,性能不低于任何手动写法。传统写法new String[0]虽然也可以,但不如方法引用直观。
大翔在技术周报上写到:"
List.of()是 Java 9 送给我们的'最小开销'礼物。它不炫技,但让每一行常量集合代码都更安全、更简洁、更高效。从今天起,飞翔科技所有新代码全面禁止Arrays.asList()用于常量定义,统一使用List.of()。"