一、介绍

一个将键映射到值的对象。Map 不能包含重复的键;每个键最多可以映射到一个值。

此接口取代了 Dictionary 类,后者是一个完全抽象的类,而不是接口。

Map 接口提供了三种集合视图,允许将 map 的内容视为键的 Set值的 Collection键值对的 Set。map 的顺序定义为集合视图的迭代器返回其元素的顺序。一些 Map 的实现,比如 TreeMap,其遍历顺序是特定;而其他实现,如 HashMap,则没有。具有特定的遍历顺序的 Map 实现通常是 SequencedMap 接口的子类型,SequencedMap 继承自 Map 接口

注意:如果使用可变对象作为映射的键,则必须格外小心。如果对象在作为映射的键时,对象中的属性被修改,并且修改会影响到 equals 方法,那么 map 接下来的行为将是不可控的。另外的一个特殊情况是,不允许将映射自己作为键放在自己里面,但是将自己作为值放在自己里面是允许的,但还是建议谨慎为好。在这种直接或间接包含自身的自引用映射示例,一些执行映射递归遍历的操作可能会产生异常并失败,这包括 clone()equals()hashCode()toString() 方法,接口的实现可以选择性地处理自引用的情况,但大多数的实现并没有这样做。

所有被设计为公用的映射实现类都应该提供两个“标准”构造函数:

  • 一个无参构造函数,它创建一个空的映射
  • 一个接受单个类型为 Map 的参数的构造函数,它创建一个新的映射,该映射具有与其参数相同的键值映射

因为具有以 Map 为参数的构造函数,实际上是允许用户复制任何映射的。虽然无法强制执行此建议(因为接口不能包含构造函数),但 JDK 中的所有通用的公共映射实现都遵循此约定。

一些 Map 的实现对其可以包含的键和值有限制。例如,一些实现禁止 null 键和 null 值,还有一些对键的类型有限制。尝试插入不符合约束条件的键或值会抛出 unchecked 异常,通常是 NullPointerExceptionClassCastException。尝试查询不符合约束条件的键或值可能会抛出异常,也可能只是返回 false,一些实现会表现出前一种行为,而另一些会表现出后一种行为。通俗的说,尝试对不符合条件的键或值执行操作,如果该操作被执行但不会导致不符合条件的元素被插入到映射中,可能会抛出异常,也可能会成功,这取决于实现类的选择。在此接口的规范中,此类异常被标记为“可选的”。

在集合框架接口中,许多方法都是基于 equals 方法定义的。例如,containsKey(Object key) 方法的规范说明:“当且仅当此映射包含一个键 k,使得 (key==null ? k==null : key.equals(k)) 时,返回 true。” 这一规范不应被理解为:使用非空参数 key 调用 Map.containsKey 时,会为任何键 k 调用 key.equals(k)。实现可以自由地进行优化,从而避免调用 equals 方法,例如,首先比较两个键的哈希码。(Object.hashCode() 规范保证,哈希码不相等的两个对象不可能相等。)更一般地说,各种集合框架接口的实现可以自由地利用底层 Object 方法的指定行为,只要实现者认为合适。

某些对映射执行递归遍历的操作可能会在自引用实例中失败并抛出异常,即映射直接或间接包含自身。这包括 clone()equals()hashCode() 和 toString() 方法。实现可以选择性地处理自引用场景,但当前大多数实现并未这样做。

二、源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
* @since 1.2
*/
public interface Map<K, V> {

// Query Operations

int size();

boolean isEmpty();

boolean containsKey(Object key);

boolean containsValue(Object value);

V get(Object key);

// Modification Operations

V put(K key, V value);

V remove(Object key);

// Bulk Operations

void putAll(Map<? extends K, ? extends V> m);

void clear();

// Views

Set<K> keySet();

Collection<V> values();

Set<Map.Entry<K, V>> entrySet();

// Comparison and hashing

boolean equals(Object o);

int hashCode();

// Defaultable methods

Map 接口中定义的方法,按照操作类型分类,可以分为:

  • 查询操作
  • 修改操作
  • 批量操作
  • 视图操作
  • 比较和哈希
  • default 方法

1、查询操作

1.1 size 方法

返回此 Map 中键值对的数量。如果映射包含的元素超过了 Integer.MAX_VALUE,则返回 Integer.MAX_VALUE

1.2 isEmpty 方法

如果此 Map 不包含键值对的映射,则返回 true

1.3 containsKey 方法

boolean containsKey(Object key);

如果此 Map 包含指定键的映射,则返回 true。

当且仅当此映射包含键 key 的映射,并且 Objects.equals(key, k) 为 true 时,返回 true。(最多只能存在一个这样的映射。)

1.4 containsValue 方法

boolean containsValue(Object value);

如果此 Map 将一个或多个键映射到指定值,则返回 true。

当且仅当此映射中至少有一个映射的值 v 满足 Objects.equals(value, v) 时,返回 true。对于大多数 Map 接口的实现,这个操作可能需要与映射大小成线性关系的时间。

1.5 get 方法

V get(Object key);

返回指定键所映射的值,如果此映射不包含该键的映射,则返回 null

如果此映射包含从键 k 到值 v 的映射,并且 Objects.equals(key, k) 为 true,则此方法返回 v;否则返回 null。(最多只能存在一个这样的映射。)

如果此映射允许 null 值,则返回 null 并不一定表示映射中不包含该键的映射;也有可能映射中明确将该键映射为 null。可以使用 containsKey 操作来区分这两种情况。

2、修改操作

2.1 put 方法

V put(K key, V value);

在此 Map 中将指定的值与指定的键关联(可选操作)。如果映射之前包含该键的映射,则旧值将被指定的值替换。(当且仅当 m.containsKey(k) 返回 true 时,可以说映射 m 包含键 k 的映射。)

2.2 remove 方法

V remove(Object key);

从此 Map 中移除键的映射(如果存在)(可选操作)。

如果此映射包含从键 k 到值 v 的映射,并且 Objects.equals(key, k) 为 true,则该映射将被移除。(映射最多只能包含一个这样的映射。)

返回此映射之前与该键关联的值,如果映射中没有该键的映射,则返回 null

如果此映射允许 null 值,那么返回 null 并不一定表示映射中没有该键的映射;也有可能映射明确将该键映射为 null
一旦调用返回,映射中将不再包含该指定键的映射。

3、批量操作

3.1 putAll 方法

void putAll(Map<? extends K, ? extends V> m);

将指定 Map 中的所有键值对复制到此映射中(可选操作)。此调用的效果相当于对指定映射中的每个键 k 和值 v,在此映射上调用一次 put(k, v)

如果在操作过程中修改了指定的映射,则此操作的行为将无法预料。如果指定的映射有明确的遍历顺序,则通常按该顺序处理其键值对。

3.2 clear 方法

void clear();

移除此 Map 中的所有键值对(可选操作)。该方法执行完成并返回后,映射将为空。

4、视图操作

4.1 键视图

Set<K> keySet();

返回此 Map 中包含的键的 Set 视图。该集合是基于此 Map 的,因此对 Map 的更改会反映在该集合中,反之亦然。如果在遍历集合时修改了映射(通过迭代器自身的 remove 操作除外),则遍历结果将是不可预料的。

该集合支持元素移除操作,包括通过 Iterator.removeSet.removeremoveAllretainAllclear 操作移除相应的映射。它不支持 addaddAll 操作。

4.2 值视图

Collection<V> values();

返回此 Map 中包含的值的 Collection 视图。该集合是基于此 Map 的,因此对 Map 的更改会反映在该集合中,反之亦然。如果在遍历集合时修改了映射(通过迭代器自身的 remove 操作除外),则遍历结果将是不可预料的。

该集合支持元素移除操作,包括通过 Iterator.removeCollection.removeremoveAllretainAllclear 操作移除相应的映射。它不支持 addaddAll 操作。

4.3 键值对视图

Set<Map.Entry<K, V>> entrySet();

返回此 Map 中包含的键值对的 Set 视图。该集合是基于此 Map 的,因此对 Map 的更改会反映在该集合中,反之亦然。如果在遍历集合时修改了映射(通过迭代器自身的 remove 操作,或通过迭代器返回的映射条目的 setValue 操作除外),则遍历结果将是不可预料的。

该集合支持元素移除操作,包括通过 Iterator.removeSet.removeremoveAllretainAllclear 操作移除相应的映射。它不支持 addaddAll 操作。

三、Map 内部接口 Entry

接口中定义了内部接口 Entry<K, V>。是一个键值对的映射。

1
2
3
4
5
6
7
8
9
10
11
12
interface Entry<K, V> {

K getKey();

V getValue();

V setValue(V value);

boolean equals(Object o);

int hashCode();
}

一个映射条目(键值对)。该条目可能是不可修改的,或者如果实现了可选的 setValue 方法,则值可能是可修改的。该条目可能独立于任何映射,也可能表示映射的 entrySet 视图中的一个条目。

如果通过迭代映射的 entrySet 视图获取条目,无论是通过显式使用 Iterator 还是通过增强的 for 语句隐式获取,条目会与其底层映射保持连接。此与底层映射的连接仅在 entrySet 视图的迭代期间有效。在迭代期间,如果底层映射支持,通过 setValue 方法对条目值的修改将在底层映射中可见。这样的条目在 entrySet 视图的迭代之外的行为是未定义的。如果在通过迭代器返回条目后,底层映射被修改(除了通过 setValue 方法修改),其行为也是未定义的。此外,底层映射中映射的值的更改,可能会或可能不会在 entrySet 视图中对应的条目中可见。

还可以通过其他方式从映射的 entrySet 视图获取条目,例如使用 parallelStreamstreamspliterator 方法、任何 toArray 的重载,或将 entrySet 视图复制到另一个集合中。是否这些获取到的条目实例与底层映射相连,是否对条目的修改会影响底层映射,反之亦然,以及这些条目是否支持可选的 setValue 方法,这些都是未指定的。

此外,可以直接从映射中获取条目,例如通过直接调用 NavigableMap 接口上的方法。这类获取的条目通常与映射没有连接,是调用时刻的映射快照,且通常是不可修改的。这样的条目一般也不支持 setValue 方法。

通过直接构造 AbstractMap.SimpleEntryAbstractMap.SimpleImmutableEntry 类,或者通过调用 Map.entryMap.Entry.copyOf 方法获得的条目与任何映射无关。

hashCode 方法,返回此对象的哈希值

1
(e.getKey() == null ? 0 : e.getKey().hashCode()) ^ (e.getValue() == null ? 0 : e.getValue().hashCode())

需要确保对于任意两个条目,如果使用 equals 方法判断是相等的,那么两个对象的 hashCode 也需要是相等的。

四、默认方法

Map 接口中提供和很多默认方法,这些方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
default V getOrDefault(Object key, V defaultValue);

default void forEach(BiConsumer<? super K, ? super V> action);

default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function);

default V putIfAbsent(K key, V value);

default boolean remove(Object key, Object value);

default boolean replace(K key, V oldValue, V newValue);

default V replace(K key, V value);

default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction);

default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction);

default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction);

V getOrDefault(Object key, V defaultValue) 方法,返回指定键在 map 中的值,如果此映射不包含键映射到的值,则返回方法参数中 defaultValue 指定的值。

void forEach(BiConsumer<? super K, ? super V> action) 方法,对此映射中的每一项都执行给定的操作,直到所有项都已处理或该操作抛出了异常。除非实现类有额外的定义,否则该操作将按照 entrySet() 方法返回的项集合的迭代器的顺序进行遍历。

replaceAll(BiFunction<? super K, ? super V, ? extends V> function) 方法,用给定函数返回的结果替换每个项的值。

V putIfAbsent(K key, V value) 方法,如果给定的键没有关联的值,或映射的值为 null,则将其与给定的值关联并返回 null,否则返回当前的值。

boolean remove(Object key, Object value) 方法,当给定的键对应给定的值的时候,删除该项。另外,有一个非 default 的方法 V remove(Object key);,会从 map 中删除指定键的项,如果键存在。

boolean replace(K key, V oldValue, V newValue) 方法,当指定键映射到指定值时,替换值为参数中的新值。

V replace(K key, V value); 方法,当指定键在 map 中存在映射的值的时候,替换值为参数中的新值。返回参数中指定键关联的旧值,如果键没有映射,则返回 null。

V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction); 方法,如果指定的键尚未关联值或映射到 null,则尝试使用给定的映射函数计算其值,并将计算结果存入到此映射中。

default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction); 方法,如果指定键的值存在且非 null,则尝试根据该键及其当前映射的值来计算新的映射。

V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction); 方法,

V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction); 方法,

五、不可变的 Map

从 Java 9 版本引入的新特性。涉及到 ImmutableCollections 类。

Map.ofMap.ofEntriesMap.copyOf 静态工厂方法提供了一种方便的方式来创建不可修改的映射。通过这些方法创建的映射实例具有以下特点:

  • 不可修改性:无法添加、删除或更新键和值。对映射调用任何修改器方法都将始终导致抛出 UnsupportedOperationException。但是,如果包含的键或值本身是可变的,这可能会导致映射表现出不一致的行为或其内容看起来发生了变化。
  • 不允许空键和值:尝试使用 null 键或 null 值创建它们会导致 NullPointerException
  • 可序列化:如果所有键和值都是可序列化的,则它们也是可序列化的。
  • 创建时拒绝重复键:传递给静态工厂方法的重复键会导致 IllegalArgumentException
  • 映射的迭代顺序未指定且可能更改:映射的迭代顺序是不确定的,并且可能会改变。

Map.of() 方法提供了 11 个实现,包括无方法参数时创建空 Map 和键值对映射从 1 对到 10 对的方法。如果需要超过 10 对的键值对映射,则需要使用 Map.ofEntries 方法。Map.ofEntries 方法使用方式如下:

1
2
3
4
5
6
7
8
import static java.util.Map.entry;

Map<Integer,String> map = Map.ofEntries(
entry(1, "a"),
entry(2, "b"),
entry(3, "c"),
...
entry(26, "z"));

可以调用 Map 的静态方法 entry(K k, V v) 来快速创建 Entry

Map.copyOf 会返回一个包含给定 Map 的条目的不可修改的 Map。给定的 Map 不能为空,且不能包含任何 null 键或值。如果随后修改了给定的 Map,返回的映射不会反映这些修改。

相关链接

OB tags

#Java #源码