跳转至

集合工厂方法:List.of / Set.of / Map.of#

版本信息

为什么需要#

JDK 8 里想「顺手创建一个小而不可变的集合」,写法很痛:

List<String> colors = Collections.unmodifiableList(Arrays.asList("red", "green", "blue"));

又长又容易忘 unmodifiableXxx,结果拿到的还「半可变」(Arrays.asListset 不可 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()));
    }
}
  1. :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 一样不保证遍历顺序,别依赖它(本例 mainTreeSet/TreeMap 排序后展示只为输出稳定);要稳定顺序用 List 或自行排序,需要插入序时转 LinkedHashSet/LinkedHashMap
  • 不接受 null:元素、键、值都不可为 null(与老 HashSet/HashMap 不同),存 nullNullPointerException
  • Map.of 最多 10 对:更多键值用 Map.ofEntries(Map.entry(k, v), ...)
  • Arrays.asList 的区别:后者是「定长、可 set 不可 add」的半可变视图;List.of 是彻底不可变,连 set 都不行。
  • 真正不可变:别忘了它仍是「不可变」,线程安全源于不可变性,不需要再包 synchronizedList

小结#

集合工厂方法是 JDK 9 起最高频的日常改进之一:一行创建不可变集合,消除了 JDK 8 的样板与「半可变」陷阱。它是写常量配置、返回快照集合的首选。

参考#