1. 概述

Java 9 终于带来了期待已久的语法糖:用一行简洁代码创建小型不可变集合实例。根据 JEP 269,JDK 9 新增了便捷的集合工厂方法。

本文将深入探讨这些方法的使用方式和实现细节。

2. 历史背景与设计动机

在 Java 中创建小型不可变集合的传统方式极其啰嗦。以 Set 为例:

Set<String> set = new HashSet<>();
set.add("foo");
set.add("bar");
set.add("baz");
set = Collections.unmodifiableSet(set);

这么简单的操作需要写这么多代码,显然应该能用单行表达式完成。Map 的情况同样糟糕。

不过 List 稍好些,有个现成的工厂方法:

List<String> list = Arrays.asList("foo", "bar", "baz");

虽然比构造器初始化简洁,但违反了最小惊讶原则——谁会想到要在 Arrays 类里找创建 List 的方法?

还有其他减少冗余的方案,比如双大括号初始化

Set<String> set = Collections.unmodifiableSet(new HashSet<String>() {{
    add("foo"); add("bar"); add("baz");
}});

或者用 Java 8 的 Stream

Stream.of("foo", "bar", "baz")
  .collect(collectingAndThen(toSet(), Collections::unmodifiableSet));

双大括号技术只是稍微简洁点,但严重牺牲可读性(属于反模式)。Java 8 版本虽是单行表达式,但也有缺陷:

  1. 不够直观
  2. 仍然冗长
  3. 会创建不必要的临时对象
  4. 无法用于创建 Map

总结下来,这些方案都没把"创建小型不可变集合"当作一等公民问题来处理。

3. 使用方法详解

ListSetMap 接口新增了静态方法,接收元素参数并返回对应的集合实例。三个接口的方法都命名为 of(...)

3.1. List 和 Set

ListSet 的工厂方法签名和特性完全相同:

static <E> List<E> of(E e1, E e2, E e3)
static <E> Set<E>  of(E e1, E e2, E e3)

使用示例:

List<String> list = List.of("foo", "bar", "baz");
Set<String> set = Set.of("foo", "bar", "baz");

简洁明了!示例中使用的是接收三个元素的重载版本,但实际提供了 12 个重载:

  • 11 个固定参数版本(0-10 个元素)
  • 1 个可变参数版本
static <E> List<E> of()
static <E> List<E> of(E e1)
static <E> List<E> of(E e1, E e2)
// ...以此类推到10个参数

static <E> List<E> of(E... elems)

⚠️ 为什么需要这么多重载?
性能!可变参数方法会隐式创建数组,固定参数版本能避免不必要的对象创建和 GC 开销。相比之下,Arrays.asList 总是创建数组,在元素少时效率较低。

创建 Set 时传入重复元素会抛出 IllegalArgumentException

@Test(expected = IllegalArgumentException.class)
public void onDuplicateElem_IfIllegalArgExp_thenSuccess() {
    Set.of("foo", "bar", "baz", "foo");
}

⚠️ 注意自动装箱
由于使用泛型,原始类型会被自动装箱。如果传入原始类型数组,返回的是包含该数组的集合:

int[] arr = { 1, 2, 3, 4, 5 };
List<int[]> list = List.of(arr); // 返回 List<int[]>,size=1

3.2. Map

Map 的工厂方法签名:

static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, K k3, V v3)

使用示例:

Map<String, String> map = Map.of("foo", "a", "bar", "b", "baz", "c");

同样提供 0-10 个键值对的重载版本。超过 10 个键值对时,使用 ofEntries 方法:

static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K,? extends V>... entries)

使用示例:

Map<String, String> map = Map.ofEntries(
  new AbstractMap.SimpleEntry<>("foo", "a"),
  new AbstractMap.SimpleEntry<>("bar", "b"),
  new AbstractMap.SimpleEntry<>("baz", "c"));

传入重复键会抛出 IllegalArgumentException

@Test(expected = IllegalArgumentException.class)
public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess() {
    Map.of("foo", "a", "foo", "b");
}

⚠️ 原始类型同样会被自动装箱

4. 实现细节

工厂方法创建的集合并非常用实现类。例如 List 不是 ArrayListMap 也不是 HashMap。它们是 Java 9 引入的特殊内部实现类,构造器访问受限。

以下是所有集合共有的关键实现特性:

4.1. 不可变性

工厂方法创建的集合完全不可变,任何修改操作都会抛出 UnsupportedOperationException

@Test(expected = UnsupportedOperationException.class)
public void onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess() {
    Set<String> set = Set.of("foo", "bar");
    set.add("baz"); // ❌ 抛出异常
}
@Test(expected = UnsupportedOperationException.class)
public void onElemModify_ifUnSupportedOpExpnThrown_thenSuccess() {
    List<String> list = List.of("foo", "bar");
    list.set(0, "baz"); // ❌ 抛出异常
}
@Test(expected = UnsupportedOperationException.class)
public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() {
    Map<String, String> map = Map.of("foo", "a", "bar", "b");
    map.remove("foo"); // ❌ 抛出异常
}

4.2. 禁止 null 元素

List/Set 不允许包含 null 元素,Map 的键和值也不能为 null。传入 null 会抛出 NullPointerException

@Test(expected = NullPointerException.class)
public void onNullElem_ifNullPtrExpnThrown_thenSuccess() {
    List.of("foo", "bar", null); // ❌ 抛出异常
}

✅ 对比:Arrays.asList 允许 null 值。

4.3. 基于值的实例

工厂方法返回的是基于值的实例。这意味着:

  • 可能返回新实例
  • 也可能返回已有实例(缓存)

因此相同值的集合可能指向不同对象:

List<String> list1 = List.of("foo", "bar");
List<String> list2 = List.of("foo", "bar");
// list1 == list2 可能返回 true 或 false,取决于 JVM 实现

4.4. 序列化支持

当集合元素可序列化时,工厂方法创建的集合也支持序列化。

5. 总结

本文介绍了 Java 9 新增的集合工厂方法。通过对比传统创建方式,我们理解了这项改进的价值。重点内容包括:

  • ✅ 使用 List.of()/Set.of()/Map.of() 创建不可变集合
  • ✅ 提供多种重载版本优化性能
  • ✅ 严格禁止 null 和重复元素
  • ✅ 完全不可变,修改操作抛出异常
  • ⚠️ 注意自动装箱和基于值实例的特性

这些集合与常用实现类(如 ArrayList/HashMap)有本质区别,使用时需特别注意其不可变性和限制条件。

完整源码及单元测试见 GitHub 项目


原始标题:Java 9 Convenience Factory Methods for Collections