1. 概述

Java 9 引入了 Map.of() 方法,使得创建不可变地图变得更加方便,以及 Map.ofEntries() 方法,它具有稍微不同的功能。

在这个教程中,我们将深入研究这两个不可变地图的静态工厂方法,并解释在何种情况下适合使用它们。

2. Map.of()

Map.of() 方法接受指定数量的键值对作为参数,返回一个包含每个键值对的不可变地图。参数中键值对的顺序与它们添加到地图中的顺序相同。如果尝试添加具有重复键的键值对,将抛出 IllegalArgumentException。如果试图添加 null 的键或值,会抛出 NullPointerException

作为重载的静态工厂方法,第一个方法允许我们创建一个空地图:

static <K, V> Map<K, V> of() {
    return (Map<K,V>) ImmutableCollections.EMPTY_MAP;
}

让我们看看它的用法:

Map<Long, String> map = Map.of();

Map<K, V> 接口中还定义了一个只接受一个键和值的方法:

static <K, V> Map<K, V> of(K k1, V v1) {
    return new ImmutableCollections.Map1<>(k1, v1);
}

我们可以这样调用它:

Map<Long, String> map = Map.of(1L, "value1");

这些工厂方法共有 11 个重载版本,最多可以接受 10 个键和 10 个值,如我们在 OpenJDK 17 中所见:

static <K, V> Map<K, V> of(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5, K k6, V v6, K k7, V v7, K k8, V v8, K k9, V v9, K k10, V v10) {
    return new ImmutableCollections.MapN<>(k1, v1, k2, v2, k3, v3, k4, v4, k5, v5, k6, v6, k7, v7, k8, v8, k9, v9, k10, v10);
}

尽管这些方法非常有用,但如果需要创建更多的方法,将会变得混乱。此外,我们不能使用 Map.of() 方法从现有键值对创建地图,因为这个方法只接受未定义的键值对作为参数。这时,Map.ofEntries() 方法就派上用场了。

3. Map.ofEntries()

Map.ofEntries() 方法接受任意数量的 Map.Entry<K, V> 对象作为参数,同样返回一个不可变地图。参数中键值对的顺序与它们添加到地图中的顺序相同。如果尝试添加具有重复键的键值对,也会抛出 IllegalArgumentException

根据 OpenJDK 17 的实现来看:

static <K, V> Map<K, V> ofEntries(Entry<? extends K, ? extends V>... entries) {
    if (entries.length == 0) { // implicit null check of entries array
        var map = (Map<K,V>) ImmutableCollections.EMPTY_MAP;
        return map;
    } else if (entries.length == 1) {
        // implicit null check of the array slot
        return new ImmutableCollections.Map1<>(entries[0].getKey(), entries[0].getValue());
    } else {
        Object[] kva = new Object[entries.length << 1];
        int a = 0;
        for (Entry<? extends K, ? extends V> entry : entries) {
            // implicit null checks of each array slot
            kva[a++] = entry.getKey();
            kva[a++] = entry.getValue();
        }
        return new ImmutableCollections.MapN<>(kva);
     }
}

使用可变参数(/java-varargs)实现,我们可以传递任意数量的条目。

例如,我们可以创建一个空地图:

Map<Long, String> map = Map.ofEntries();

或者创建并填充地图:

Map<Long, String> longUserMap = Map.ofEntries(Map.entry(1L, "User A"), Map.entry(2L, "User B"));

Map.ofEntries() 方法的一个主要优势是,我们还可以使用它从现有键值对创建地图。这在 Map.of() 方法中是无法做到的,因为它只接受未定义的键值对作为参数。

4. 总结

Map.of() 方法仅适用于最多包含 10 个元素的地图,因为它是通过 11 个不同的重载方法实现的,这些方法分别接受 0 到 10 个键值对。另一方面,Map.ofEntries() 方法可以用于任何大小的地图,因为它利用了可变参数(/java-varargs)特性

完整的示例可在 GitHub 上找到。