应优先使用 ImmutableList 而非 ArrayList 当集合构建后永不修改且需多线程安全读取;它避免同步开销与并发异常,适用于配置列表、状态码白名单、只读返回值及 Map 的 key。

什么时候该用 ImmutableList 而不是 ArrayList
当你确定集合构建后绝不会被修改(增删改),且多个线程可能同时读取它时,ImmutableList 是更安全、更轻量的选择。它省去了同步开销,也杜绝了意外的 add() 或 set() 导致的 ConcurrentModificationException 或逻辑错乱。
常见场景包括:
- 配置项列表(如支持的协议名:
ImmutableList.of("http", "https", "grpc")) - 枚举对应的状态码映射集合(如 HTTP 状态码白名单)
- 方法返回值中“只读视图”——避免调用方误改内部状态
- 作为
Map的 key(因不可变,hashCode稳定,适合做哈希结构的键)
ImmutableSet.copyOf() 为什么比 new HashSet(list) 更值得考虑
两者都能去重,但行为差异明显:
-
ImmutableSet.copyOf(list)在list为null时直接抛NullPointerException,不静默失败;若含重复元素,它自动去重且保证顺序(基于插入顺序的不可变实现) -
new HashSet(list)允许null元素(除非元素本身不允许),但不保证遍历顺序,且后续可被任意修改 - 性能上,
ImmutableSet.copyOf()对小集合(≤5 元素)使用优化的单例或数组实现,内存占用更低;大集合则用紧凑哈希表,比HashSet少约 20% 内存(无扩容冗余、无空桶)
注意:如果 list 含可变对象(如自定义类),ImmutableSet 只保证集合结构不可变,不递归冻结元素内容。
立即学习“Java免费学习笔记(深入)”;
用 ImmutableMap.ofEntries() 构建配置映射时的坑
这是 Java 9+ 推荐的不可变 map 构建方式,但容易踩两个隐性问题:
- 传入的
Map.Entry若来自可变 map(比如someMap.entrySet().iterator().next()),而原 map 后续被修改,entry 的getValue()可能变化——ImmutableMap不深拷贝值,只引用 - 键或值为
null会直接抛NullPointerException(连ImmutableMap.of()都不支持null键值) - 如果需要默认值 fallback,别写
map.get(key) != null ? map.get(key) : "default"——两次查找。应改用map.getOrDefault(key, "default"),它在不可变 map 上是 O(1) 且安全
ImmutableMapportMap = ImmutableMap.ofEntries( Map.entry("redis", 6379), Map.entry("postgres", 5432) );
替代 Collections.unmodifiableXXX() 的真实理由
Collections.unmodifiableList() 看似免费,但它是“包装器”:底层仍持有一个可变集合引用。一旦原始集合被其他代码修改,不可修改视图也会跟着变——这违背了“不可变”的直觉和契约。
而 ImmutableList.copyOf() 等 Guava(或 JDK9+ List.of())是**真正复制并冻结**:
- 构造即快照,与源集合彻底解耦
- 没有隐藏的“背后可变”风险
- 运行时类型明确(如
RegularImmutableList),便于调试和序列化识别
所以,只要不是极端受限于依赖(比如不能引 Guava 或必须兼容 Java 8),优先选真正的不可变实现。JDK9+ 的 List.of()、Set.of()、Map.of() 已覆盖大部分简单场景,但注意它们不接受 null,且 Map.of() 最多支持 10 个键值对,超限得用 Map.ofEntries()。










