集合工厂方法:List.of / Set.of / Map.of#
版本信息
- 最低 JDK:9(随 JDK 9 引入,11 LTS 承接)
- JEP:JEP 269: Convenience Factory Methods for Collections
为什么需要#
JDK 8 里想「顺手创建一个小而不可变的集合」,写法很痛:
List<String> colors = Collections.unmodifiableList(Arrays.asList("red", "green", "blue"));
又长又容易忘 unmodifiableXxx,结果拿到的还「半可变」(Arrays.asList 可 set 不可 add)。JEP 269 给 List/Set/Map 各加了 of 静态工厂方法,一行创建真正不可变的集合,且对小型集合有专门的紧凑实现,内存与性能都更优。
语法#
examples/collection-factories/ 里真实可运行的源码(snippets 嵌入,与示例零漂移):
package com.javamodern.collfactory;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
// JEP 269(JDK 9):List.of / Set.of / Map.of 一步创建「不可变」集合,
// 取代 JDK 8 里 Arrays.asList + Collections.unmodifiableXxx 的啰嗦写法。
public class CollectionFactoryDemo {
/** List.of:不可变 List。 */
public static List<String> colors() {
return List.of("red", "green", "blue"); // (1)
}
/** Set.of:不可变 Set(传入重复元素会抛 IllegalArgumentException)。 */
public static Set<Integer> nums() {
return Set.of(1, 2, 3);
}
/** Map.of:不可变 Map(键值成对传入,≤10 对有重载;更多用 Map.ofEntries)。 */
public static Map<String, Integer> scores() {
return Map.of("Java", 8, "Kotlin", 1);
}
public static void main(String[] args) {
System.out.println("colors = " + colors());
// Set.of / Map.of 的迭代顺序未指定,用 TreeSet/TreeMap 排序展示,保证输出稳定可复现
System.out.println("nums = " + new TreeSet<>(nums()));
System.out.println("scores = " + new TreeMap<>(scores()));
}
}
- :material-lightbulb:
List.of(...)/Set.of(...)/Map.of(...)返回不可变集合——对它们add/put/remove会抛UnsupportedOperationException。
运行 CollectionFactoryDemo.main 的实测输出(JDK 11):
colors = [red, green, blue]
nums = [1, 2, 3]
scores = {Java=8, Kotlin=1}
与 JDK 8 旧写法对比#
List<String> colors = Collections.unmodifiableList(
Arrays.asList("red", "green", "blue")); // 忘 unmodifiable 就可变
Map<String, Integer> m = new HashMap<>();
m.put("Java", 8);
Map<String, Integer> scores = Collections.unmodifiableMap(m); // 三行起步
List<String> colors = List.of("red", "green", "blue");
Set<Integer> nums = Set.of(1, 2, 3);
Map<String, Integer> scores = Map.of("Java", 8, "Kotlin", 1); // 键值成对
Map<String, Integer> big = Map.ofEntries(
Map.entry("Java", 8), Map.entry("Kotlin", 1) /* , ... */); // >10 对用 ofEntries
底层原理#
of 方法对常见元数(0~10 个元素)做了重载,超过的用 Map.ofEntries。返回的实例是专门的紧凑类型:例如只有 1~2 个元素的 Map 用单字段/双字段实现,省去桶数组;空集合返回共享单例(List.of() 恒等同一实例)。这些实现都拒绝 null 元素/键/值,并在构造时即固化、之后任何修改操作一律 UnsupportedOperationException。
graph LR
F["List.of / Set.of / Map.of"] --> I["紧凑不可变实现<br/>(按元素数选最优)"]
I --> N["拒绝 null"]
I --> U["修改 → UnsupportedOperationException"]
F --> S["0 元素 → 共享单例"]
style F fill:#bbdefb
style U fill:#ffcdd2
常见坑 / 最佳实践#
Set.of拒绝重复元素:传重复值直接抛IllegalArgumentException(不是静默去重),构造期就暴露 bug。Set.of/Map.of迭代顺序未指定:与HashSet/HashMap一样不保证遍历顺序,别依赖它(本例main用TreeSet/TreeMap排序后展示只为输出稳定);要稳定顺序用List或自行排序,需要插入序时转LinkedHashSet/LinkedHashMap。- 不接受
null:元素、键、值都不可为null(与老HashSet/HashMap不同),存null抛NullPointerException。 Map.of最多 10 对:更多键值用Map.ofEntries(Map.entry(k, v), ...)。- 与
Arrays.asList的区别:后者是「定长、可set不可add」的半可变视图;List.of是彻底不可变,连set都不行。 - 真正不可变:别忘了它仍是「不可变」,线程安全源于不可变性,不需要再包
synchronizedList。
小结#
集合工厂方法是 JDK 9 起最高频的日常改进之一:一行创建不可变集合,消除了 JDK 8 的样板与「半可变」陷阱。它是写常量配置、返回快照集合的首选。