Java中的不可变Map实现

2023/06/07

1. 概述

有时最好禁止修改java.util.Map,例如跨线程共享只读数据。为此,我们可以使用UnmodifiableMap或ImmutableMap。

在这个快速教程中,我们将了解它们之间的区别。然后,我们将介绍创建不可变Map的各种方法。

2. 不可修改与不可变

UnmodifiableMap只是对可修改Map的包装,它不允许直接对其进行修改

Map<String, String> mutableMap = new HashMap<>();
mutableMap.put("USA", "North America");

Map<String, String> unmodifiableMap = Collections.unmodifiableMap(mutableMap);
assertThrows(UnsupportedOperationException.class,
    () -> unmodifiableMap.put("Canada", "North America"));

但是底层的可变Map仍然可以更改,并且修改也会反映在不可修改的Map中:

mutableMap.remove("USA");
assertFalse(unmodifiableMap.containsKey("USA"));
		
mutableMap.put("Mexico", "North America");
assertTrue(unmodifiableMap.containsKey("Mexico"));

另一方面,不可变Map包含其自己的私有数据并且不允许对其进行修改。因此,一旦创建了ImmutableMap的实例,数据就不能以任何方式更改。

3. Guava的ImmutableMap

Guava使用ImmutableMap提供了每个java.util.Map的不可变版本。每当我们尝试修改它时,它都会抛出UnsupportedOperationException。

由于它包含自己的私有数据,因此当原始Map发生变化时,这些数据不会改变。

现在我们将讨论创建ImmutableMap实例的各种方法。

3.1 使用copyOf()方法

首先,让我们使用ImmutableMap.copyOf()方法,该方法返回原始Map中所有条目的副本:

ImmutableMap<String, String> immutableMap = ImmutableMap.copyOf(mutableMap);
assertTrue(immutableMap.containsKey("USA"));

它不能直接或间接修改:

assertThrows(UnsupportedOperationException.class,
    () -> immutableMap.put("Canada", "North America"));
		
mutableMap.remove("USA");
assertTrue(immutableMap.containsKey("USA"));
		
mutableMap.put("Mexico", "North America");
assertFalse(immutableMap.containsKey("Mexico"));

3.2 使用builder()方法

我们还可以使用ImmutableMap.builder()方法创建原始Map中所有条目的副本。

此外,我们可以使用此方法添加原始Map中不存在的其他条目:

ImmutableMap<String, String> immutableMap = ImmutableMap.<String, String>builder()
    .putAll(mutableMap)
    .put("Costa Rica", "North America")
    .build();
assertTrue(immutableMap.containsKey("USA"));
assertTrue(immutableMap.containsKey("Costa Rica"));

与前面的示例相同,我们不能直接或间接修改它:

assertThrows(UnsupportedOperationException.class,
    () -> immutableMap.put("Canada", "North America"));
		
mutableMap.remove("USA");
assertTrue(immutableMap.containsKey("USA"));
		
mutableMap.put("Mexico", "North America");
assertFalse(immutableMap.containsKey("Mexico"));

3.3 使用of()方法

最后,我们可以使用ImmutableMap.of()方法创建一个不可变Map,其中包含一组动态提供的条目。它最多支持5个键/值对

ImmutableMap<String, String> immutableMap = ImmutableMap.of("USA", "North America", "Costa Rica", "North America");
assertTrue(immutableMap.containsKey("USA"));
assertTrue(immutableMap.containsKey("Costa Rica"));

我们也不能修改它:

assertThrows(UnsupportedOperationException.class,
    () -> immutableMap.put("Canada", "North America"));

3.4 使用ofEntries()方法

最后,我们可以使用ImmutableMap.ofEntries()方法创建一个不可修改的Map,其中包含从给定条目中提取的键和值。

与ImmutableMap.of()不同,我们可以将任意数量的条目作为参数传递给此方法

现在,让我们举例说明ImmutableMap.ofEntries()方法的使用:

ImmutableMap<Integer, String> immutableMap = ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"));
assertEquals(1, immutableMap.size());
assertThat(immutableMap, IsMapContaining.hasEntry(1, "USA"));

如我们所见,我们使用了AbstractMap类来创建Map条目。

同样,尝试修改返回的Map将导致抛出UnsupportedOperationException:

ImmutableMap<Integer, String> immutableMap = ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"), new AbstractMap.SimpleEntry<>(2, "Canada"));
assertThrows(UnsupportedOperationException.class, () -> immutableMap.put(2, "Mexico"));

通常,如果我们添加具有重复键的条目,该方法会抛出IllegalArgumentException:

assertThrows(IllegalArgumentException.class,
    () -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, "USA"), new AbstractMap.SimpleEntry<>(1, "Canada")));

请注意,ImmutableMap.ofEntries()不接受null作为键或值。

因此,尝试使用null键指定条目将导致NullPointerException:

assertThrows(NullPointerException.class,
    () -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(null, "USA")));

并且,将null指定为值也会导致NullPointerException:

assertThrows(NullPointerException.class,
    () -> ImmutableMap.ofEntries(new AbstractMap.SimpleEntry<>(1, null)));

4. 总结

在这篇简短的文章中,我们讨论了UnmodifiableMap和ImmutableMap之间的区别。

我们还了解了创建Guava的ImmutableMap的不同方法。

与往常一样,本教程的完整源代码可在GitHub上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章