在 Java 集合体系中,Map 接口自 9 版本起提供了一组名为 of 的静态方法,它们属于典型的抽象工厂方法:调用方无需关心具体实现类,只声明“我需要一个不可变映射”,由 JVM 返回合适的内部类型。这种方式非常适合用来构建只读字典,例如系统错误码对照、环境变量快照或固定业务参数表。

一、Map.of 的基本用法与语法形态
Map.of 提供了从零个到十个键值对的重载方法,以及接收变长参数的 ofEntries 方法。这些方法返回的对象都是不可变的,任何 put、remove、putAll 调用都会抛出 UnsupportedOperationException。对于只读字典而言,这种约束正是我们想要的:一旦创建,结构稳定。
下面展示最常用的几个创建方式。注意键值都不能为 null,否则在调用瞬间就会抛出 NullPointerException,而不是推迟到后续操作。
import java.util.Map;
public class ReadOnlyDictDemo {
public static void main(String[] args) {
// 空字典
Map<String, Integer> empty = Map.of();
// 单键值对
Map<String, Integer> one = Map.of("timeout", 30);
// 多键值对
Map<String, String> config = Map.of(
"env", "prod",
"region", "cn-north",
"logLevel", "INFO"
);
System.out.println(config.get("env"));
}
}
上述代码在编译后,Map.of 会根据参数数量挑选如 ImmutableCollections.Map1、MapN 等私有实现。它们不持有可修改的哈希表结构,而是用更紧凑的数组或单字段存储,因此内存占用低于 HashMap 加包装层。
当键值超过十个时,必须使用 ofEntries 配合 Map.entry 静态方法。这种方式虽然稍显冗长,但能支持任意数量,且同样返回不可变实例。
import java.util.Map;
public class LargeDictDemo {
public static void main(String[] args) {
Map<String, Integer> scores = Map.ofEntries(
Map.entry("alice", 90),
Map.entry("bob", 85),
Map.entry("carol", 92),
Map.entry("dave", 78)
);
// scores.put("eve", 88); // 这里会抛 UnsupportedOperationException
}
}
二、不可变特性的底层表现与异常场景
不少团队在迁移旧代码时会误以为 Map.of 返回的对象只是“没人改”的普通 Map,实际上它的实现类彻底移除了修改通道。调用修改类方法不仅失败,而且失败是立即且确定的,有助于尽早暴露设计漏洞。
空值是另一个易错点。在 HashMap 中我们可以放 null 值,但 Map.of 明确禁止。如果字典需要从外部数据转换且可能含空,应先做过滤或改用其他构建方式,否则会直接崩溃。
import java.util.Map;
public class NullForbidden {
public static void main(String[] args) {
try {
// 键或值为 null 都会抛 NullPointerException
Map<String, String> bad = Map.of("key", null);
} catch (NullPointerException e) {
System.out.println("不允许 null 值: " + e.getMessage());
}
try {
Map<String, String> mutable = new java.util.HashMap<>();
mutable.put("a", "1");
// 以下调用会抛 UnsupportedOperationException
Map.of("x", "y").put("z", "w");
} catch (UnsupportedOperationException e) {
System.out.println("只读字典不可修改");
}
}
}
从抽象工厂视角看,Map.of 隐藏了 ImmutableCollections 的具体子类选择逻辑。调用者只依赖 Map 接口,这符合依赖倒置原则,也让单元测试中构造假数据变得更简单:一行代码即可得到稳定的字典桩。
性能上,由于不可变对象天然线程安全,它们可以被多个线程共享而不加锁。在高频读取的配置中心客户端里,用 Map.of 构建快照比每次 new HashMap 再同步更高效。
三、与旧方案 Collections.unmodifiableMap 的对比
在 Map.of 出现前,我们常用 Collections.unmodifiableMap 把可变 Map 包一层。这种方式的问题是底层仍依赖原 HashMap,若原引用被泄露,外部仍可修改;而且包装对象本身有额外方法调用开销。
下面的表格列出两者差异,方便在实战中取舍。
| 维度 | Map.of | Collections.unmodifiableMap |
|---|---|---|
| 空值支持 | 不支持 | 支持(取决于底层 Map) |
| 底层可变性 | 完全不可变 | 底层可变若引用泄露 |
| 语法简洁度 | 高 | 低 |
| 适用数量 | 0-10 直接重载,以上用 ofEntries | 任意 |
如果确实需要从可能包含空值的动态数据生成只读字典,可先填入 HashMap 再做防御性拷贝,最后用 Map.copyOf 得到不可变副本。Map.copyOf 会拒绝 null,但能接收任意现有 Map,是介于两者之间的实用选择。
import java.util.HashMap;
import java.util.Map;
public class CopyOfDemo {
public static void main(String[] args) {
HashMap<String, Integer> temp = new HashMap<>();
temp.put("max", 100);
temp.put("min", 0);
// 得到不可变副本,temp 后续修改不影响它
Map<String, Integer> readonly = Map.copyOf(temp);
temp.put("max", 200);
System.out.println(readonly.get("max")); // 仍输出 100
}
}
总结来说,在新建只读字典且数据确定不含空值时,优先使用 Map.of 系列抽象工厂方法。它用最简语法提供真正不可变、线程安全、内存友好的映射,是实战中替代手工锁和包装层的最佳默认选项。
Map_ofimmutable_mapabstract_factory修改时间:2026-08-01 20:00:30