Map
Map
是将键映射到值的对象。它不能包含重复的键;每个键最多可以映射到一个值。
这个接口取代了Dictionary
类,Dictionary
是一个完全抽象的类而不是一个接口。
Map
接口提供三种集合视图,允许将map的内容视为键的集合、值的集合或键值映射的集合。map的顺序被定义为map集合视图上的迭代器返回其元素的顺序。一些Map
实现,比如TreeMap
类,对它们的顺序做出了具体的保证;其他的,比如HashMap
类便没有这种保证。
注意:如果将可变对象用作map的键,则必须非常小心。如果对象的值以影响equals
比较的方式更改,而该对象又是映射中的键,则此时map的行为是不确定的。此禁令的一个特殊情况是不允许map将自身作为键。虽然允许map将自身包含为值,但建议格外小心:equals
和hashCode
方法未在此类map上明确定义。
所有通用Map
实现类都应该提供两个“标准”构造函数:一个创建空map的void
(无参)构造函数,以及一个类型为 Map
的单个参数的构造函数,它将创建一个新map并具有与其参数中指定map相同的键值映射。实际上,后一个构造函数允许用户复制任何map,生成所需类的等效map。没有办法强制执行此建议(因为接口不能包含构造函数),但 JDK 中的所有通用map实现都符合此规范。
此接口中包含的“破坏性”方法,即操作它们时修改map的方法,如果此map不支持该操作,则指定为抛出UnsupportedOperationException
。在这种情况下,如果调用对map没有影响,则这些方法可能(但不是必需)抛出UnsupportedOperationException
。例如,在不可修改的map上调用putAll(Map)
方法,如果要“叠加”的map为空的话则可能(但不是必须)抛出异常。
一些Map
实现对它们可能包含的键和值有限制。例如,有些实现禁止空的键和值,有些实现对其键的类型有限制。尝试插入不合格的键或值会引发未经检查的异常,通常是NullPointerException
或ClassCastException
。尝试查询不合格的键或值可能会引发异常,或者它可能只是返回false
;一些实现会表现出前一种行为,而另一些会表现出后者。更一般地,尝试对不合格的键或值执行操作,其完成不会导致不合格的元素插入到map中,可能会引发异常,也可能会成功,具体取决于实现的选择。在此接口的规范中,此类异常被标记为“可选”。
集合框架接口中的许多方法都是根据equals
方法定义的。例如,containsKey(Object key)
方法的规范说:“当且仅当此map包含键k
使得(key==null ? k==null : key.equals(k))
返回true
。”本规范不应被解释为意味着使用非空参数key
调用Map.containsKey
将导致为任何键k
调用key.equals(k)
。实现类可以自由地进行优化,从而避免equals
调用,例如,通过首先比较两个键的哈希码。Object
的hashCode()
规范保证哈希码不相等的两个对象不能相等。)更一般地说,各种集合框架接口的实现可以在实现者认为合适的任何地方自由地利用底层Object
方法的指定行为。
执行map某些递归遍历的操作可能会失败,但map直接或间接包含自身的自引用实例除外。这包括clone()
、equals()
、hashCode()
和toString()
方法。实现可以选择性地处理自引用场景,但是大多数当前的实现都没有这样做。
该接口是Java集合框架的成员。
返回此map中键值映射的数量。如果map包含多于Integer.MAX_VALUE
个元素,则返回Integer.MAX_VALUE
。
如果此map不包含键值映射,则返回true
。
如果此map包含指定键的映射,则返回true
。更正式地说,当且仅当此map包含键k
的映射使得(key==null ? k==null : key.equals(k))
则返回true
。 (最多可以有一个这样的映射。)
如果此映射能将一个或多个键映射到指定的值,则返回true
。更正式地说,当且仅当此映射包含至少一个映射的值v
使得(value==null ? v==null : value.equals(v))
。对于Map
接口的大多数实现,此操作可能需要与map大小成线性关系的时间。
返回指定的键所映射的值,如果此map不包含该键的映射,则返回null
。
更正式地说,如果此映射包含从键k
到值v
的映射,使得(key==null ? k==null : key.equals(k))
,则方法返回v
,否则返回null
。 (最多可以有一个这样的映射。)
如果此map允许空值,则null
的返回值并不一定表示该map不包含该键的映射,也有可能map将键显式映射到null
。containsKey
操作可用于区分这两种情况。
在此map中将指定值与指定键相关联(可选操作)。如果map先前已包含键的映射,则旧值将替换为指定值。 (判断一个map m
包含键k
的映射,需满足的前提条件是:当且仅当m.containsKey(k)
将返回true
。)
此map中如果存在指定键的映射,则将其删除(可选操作)。更正式地说,如果此map
包含从键k
到值v
的映射,使得(key==null ? k==null : key.equals(k))
,则该映射将被删除。 (map最多可以包含一个这样的映射。)
返回此map先前与键关联的值,如果map不包含键的映射,则返回null
。
如果此map允许空值,则返回值null
并不一定表示该map不包含该键的映射,也有可能映射显式地将键映射到null
(也就是存在某个key对应的value为null
)。
一旦调用该方法获得返回值,map将不再包含指定键的映射。
将所有映射从指定map复制到当前map(可选操作)。这个调用的效果相当于在当前map上调用put(k, v)
将指定map中的每个从键k
到值v
的映射都添加到当前map中。如果在操作进行时修改了指定的map,则此操作的行为将变得不确定。
从此map中删除所有映射(可选操作)。此调用返回后,map将为空。
返回此map中包含的键的Set
视图。该集合基于当前map,因此对map的更改会反映在该集合中,反之亦然。如果在对集合进行迭代时修改了map(通过迭代器自己的remove
操作除外),则迭代的结果是不确定的。该集合支持元素移除,即通过Iterator.remove
、Set.remove
、removeAll
、retainAll
和clear
操作从map中移除相应的映射。它不支持add
或addAll
操作。
返回此map中包含的值的Collection
视图。集合基于当前map,因此对map的更改会反映在集合中,反之亦然。如果在对集合进行迭代时修改了map(通过迭代器自己的remove
操作除外),则迭代的结果是不确定的。该集合支持元素移除,即通过Iterator.remove
、Collection.remove
、removeAll
、retainAll
和clear
操作从map中移除相应的映射。它不支持add
或addAll
操作。
返回此map中包含映射的Set
视图。该集合由map支持,因此对map的更改会反映在该集合中,反之亦然。如果在对集合进行迭代时修改了map(通过迭代器自己的remove
操作或通过迭代器返回的映射条目上的setValue
操作除外),则迭代的结果是不确定的。该集合支持元素移除,即通过Iterator.remove
、Set.remove
、removeAll
、retainAll
和clear
操作从map中移除相应的映射。它不支持add
或addAll
操作。
一个map条目(键值对)。Map.entrySet
方法返回map的集合视图,其元素属于此类。获取映射条目引用的唯一方法来自此集合视图的迭代器。这些Map.Entry
对象仅在迭代期间是有效的;更正式地说,如果在迭代器返回条目后修改了后备map,则map条目的行为是未定义的,除非是通过对映射条目的setValue
操作。
返回当前条目对应的键。
返回当前条目对应的值。如果映射已从后备map中被删除(通过迭代器的remove
操作),则此调用的结果是未定义的。
用指定的值替换此条目当前对应的值(可选操作)。 (会写入map。)如果映射已经从map中删除(通过迭代器的remove
操作),则此调用的行为未定义。
比较指定的对象是否与当前条目相等。如果给定对象也是map的条目并且这两个条目表示相同的映射,则返回true
。
更准确的说,两个条目如果满足如下条件则认为他们代表相同的映射:
(e1.getKey()==null ?
e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
(e1.getValue()==null ?
e2.getValue()==null : e1.getValue().equals(e2.getValue()))
这确保equals
方法在Map.Entry
接口的不同实现中正常工作。
返回此map条目的哈希代码值。map条目e
的哈希码定义为:
(e.getKey()==null ? 0 : e.getKey().hashCode()) ^
(e.getValue()==null ? 0 : e.getValue().hashCode())
这确保e1.equals(e2)
就意味着对于任意两个条目e1
和e2
都有e1.hashCode()==e2.hashCode()
,这是遵循了Object.hashCode
的通用规范的要求。
返回一个比较器,它按键的自然顺序比较Map.Entry
。
返回的比较器是可序列化的,并在比较键为null
的条目时抛出NullPointerException
。
源码如下:
public static <K extends Comparable<? super K>, V> Comparator<Map.Entry<K,V>> comparingByKey() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
返回一个比较器,它按值的自然顺序比较Map.Entry
。
返回的比较器是可序列化的,并在比较值为null
的条目时抛出NullPointerException
。
源码如下:
public static <K, V extends Comparable<? super V>> Comparator<Map.Entry<K,V>> comparingByValue() {
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> c1.getValue().compareTo(c2.getValue());
}
返回一个比较器,该比较器使用给定的Comparator
按键比较Map.Entry
。
如果指定的比较器是可序列化的,则返回的比较器也是可序列化的。
源码如下:
public static <K, V> Comparator<Map.Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
返回一个比较器,该比较器使用给定的Comparator
按键比较Map.Entry
。
如果指定的比较器是可序列化的,则返回的比较器也是可序列化的。
源码如下:
public static <K, V> Comparator<Map.Entry<K, V>> comparingByValue(Comparator<? super V> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Map.Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getValue(), c2.getValue());
}
比较指定对象是否与当前map相等。如果给定的对象也是一个map并且这两个map表示相同的映射,则返回true
。更正式地说,如果两个mapm1
、m2
满足m1.entrySet().equals(m2.entrySet())
,则m1
和m2
表示相同的映射。这确保equals
方法在Map
接口的不同实现中正常工作。
返回此map的哈希码值。map的哈希码定义为map的entrySet()
视图中每个条目的哈希码之和。这确保对于任何两个map m1
和m2
来说m1.equals(m2)
意味着m1.hashCode()==m2.hashCode()
,这也是Object
的hashCode
通用规范要求。
返回指定键所映射到的值,如果此map不包含该键的映射,则返回defaultValue
。
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
源码如下:
default V getOrDefault(Object key, V defaultValue) {
V v;
// 此处先调用get方法取出指定key对应的value,如果value不为空则直接返回该value
// 如果value为空,则调用containsKey判断当前map是否包含键key,这是为了避免
// 当前map存在指定的键key,但该键对应的value为空的情况。
// 当value不为空时就没有调用containsKey进行判断的必要了,此处很巧妙。
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
对此map中的每个条目执行给定的操作,直到处理完所有条目或该操作引发异常。除非实现类另有规定,否则将按照条目集合迭代的顺序执行操作(如果指定了迭代顺序)。该操作抛出的异常将转发给调用者。
默认实现相当于对此map:
for (Map.Entry<K, V> entry : map.entrySet())
action.accept(entry.getKey(), entry.getValue());
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
源码如下:
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
// 先调用entrySet()方法获取当前map的Set视图,然后遍历该Set
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// 此处需要try catch起来是因为该默认实现不保证同步,所以存在并发修改的可能
// 有可能在使用getKey()等方法时此条目已不存在
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// 执行给定操作
action.accept(k, v);
}
}
将每个条目的值替换为对该条目调用给定函数获得的结果,直到处理完所有条目或该函数抛出异常。函数抛出的异常将被转发给调用者。
默认实现相当于对此map:
for (Map.Entry<K, V> entry : map.entrySet())
entry.setValue(function.apply(entry.getKey(), entry.getValue()));
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
// 先调用entrySet()获取当前map的Set视图,然后遍历该Set
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
// 从函数抛出的ise不是cme。
// ise thrown from function is not a cme.
// 对当前条目的key和value执行给定的操作,得到一个值
v = function.apply(k, v);
// 将该值赋值给当前条码的value
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
如果指定的键尚未与值关联(或映射到了null
),则将其与给定值进行关联并返回null
,否则返回当前值。
默认实现相当于对此map:
V v = map.get(key);
if (v == null)
v = map.put(key, value);
return v;
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
源码如下:
default V putIfAbsent(K key, V value) {
// 调用get方法获取指定key的值
V v = get(key);
// 如果该值为null,则调用put方法将指定的value与该key进行关联
// 此处未用containsKey进行判断,所以,即使当前map已包含指定key(但对应的value为空),此处也会为其设置新值
if (v == null) {
v = put(key, value);
}
// 返回原值或指定的value
return v;
}
仅当当前map到指定值时,才删除指定键的条目。
默认实现相当于对此map:
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.remove(key);
return true;
} else
return false;
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
源码如下:
default boolean remove(Object key, Object value) {
// 取出指定key的值
Object curValue = get(key);
// 判断取出的值是否与参数指定的值相等,若不相等则不能删除,此时返回true
// 若相等,则看取出的值是否为空,如果为空的话则需要判断当前map是否包含指定的key,若不包含,则也不能删除
if (!Objects.equals(curValue, value) ||
(curValue == null && !containsKey(key))) {
return false;
}
// 执行删除,返回true
remove(key);
return true;
}
仅当指定的键映射到指定值时才替换指定键的值。
默认实现相当于对此map:
if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
map.put(key, newValue);
return true;
} else
return false;
如果oldValue
为 null
,则默认实现不会为不支持null
值的map抛出NullPointerException
,除非newValue
也为null
。
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
源码如下:
default boolean replace(K key, V oldValue, V newValue) {
// 取出指定key对应的值
Object curValue = get(key);
// 如果取出的值与参数指定的旧值不同,则不能做替换,此时返回false
// 若相同,还有可能是二者都为null,若是都为null,则判断当前map是否包含指定的键,若不包含,也不能替换,此时返回false
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
仅当指定键映射到某个值(值也可以是null
,但是当前map必须要包含该key)时才替换指定键的条目。
默认实现相当于对此map:
if (map.containsKey(key)) {
return map.put(key, value);
} else
return null;
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。
default V replace(K key, V value) {
V curValue;
// 先取出指定key对应的值
// 再判断该值是否为空,不为空,则可以替换值,然后返回替换后的值
// 如果取出的值是null,则判断当前map是否包含该key,若包含该key,也可以予以替换
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
如果指定的键尚未与值关联(或映射到null
),则尝试使用给定的映射函数计算其值并将其放入此map,除非计算的结果为null
。
如果函数返回null
,则不会记录任何映射。如果函数本身抛出(未经检查的)异常,则重新抛出异常,并且不记录任何映射。最常见的用法是构造一个新对象作为初始映射值或内存结果,如下所示:
map.computeIfAbsent(key, k -> new Value(f(k)));
或者实现一个多值映射,Map<K,Collection<V>>
,支持每个键多个值:
map.computeIfAbsent(key, k -> new HashSet<V>()).add(v);
默认实现相当于对此map执行以下步骤,然后返回当前值或null
(如果当前不存在):
if (map.get(key) == null) {
V newValue = mappingFunction.apply(key);
if (newValue != null)
map.put(key, newValue);
}
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。特别是,子接口java.util.concurrent.ConcurrentMap
的所有实现都必须记录该函数是否仅在该值不存在时以原子方式应用一次。
源码如下:
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
// 只有取出的值为空,才执行给定的函数
if ((v = get(key)) == null) {
V newValue;
// 只有指定的函数执行的结果不为空才将其放入map
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
24. default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
如果指定键的值存在且非空,则尝试计算出一个值,并把这个值映射到该键上。
如果函数返回null
,则删除该映射。如果函数本身抛出(未经检查的)异常,则当前方法将重新抛出异常,并且当前映射保持不变。
默认实现相当于对此map执行以下步骤,然后返回当前值或null
(如果不存在):
if (map.get(key) != null) {
V oldValue = map.get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null)
map.put(key, newValue);
else
map.remove(key);
}
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。特别是,子接口java.util.concurrent.ConcurrentMap
的所有实现都必须记录该函数是否仅在该值不存在时以原子方式应用一次。
源码如下:
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
// 先取出指定键对应的值,若该值不为空则对其执行给定的函数
if ((oldValue = get(key)) != null) {
// 基于指定的key和该key原先的值计算出一个新值
V newValue = remappingFunction.apply(key, oldValue);
// 判断新值是否为空
if (newValue != null) {
// 若不为空,则将其映射到当前key,然后返回该新值
put(key, newValue);
return newValue;
} else {
// 若为空,则将该key从当前map中删除,然后返回null
remove(key);
return null;
}
} else {
return null;
}
}
尝试计算指定键及其当前映射值(如果没有当前映射,则为null
)的映射。例如,要创建或拼接String
的msg
到值映射:
// 通常merge()方法用于此场景会更简单。
map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
如果函数返回null
,则删除映射(如果最初不存在,则保持不存在)。如果函数本身抛出(未经检查的)异常,则当前方法会重新抛出异常,并且当前映射保持不变。
默认实现等同于对当前map执行以下步骤的操作,然后返回当前值或者null
(如果不存在):
V oldValue = map.get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (oldValue != null ) {
if (newValue != null)
map.put(key, newValue);
else
map.remove(key);
} else {
if (newValue != null)
map.put(key, newValue);
else
return null;
}
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。特别是,子接口java.util.concurrent.ConcurrentMap
的所有实现都必须记录该函数是否仅在该值不存在时以原子方式应用一次。
源码如下:
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
// 先取出当前key对应的旧值
V oldValue = get(key);
// 对当前key和对应的旧值执行给定重映射函数,得到一个新值
V newValue = remappingFunction.apply(key, oldValue);
// 判断新值是否为空
if (newValue == null) {
// 新值为空,则判断旧值或者当前map中是否存在当前key
// delete mapping
if (oldValue != null || containsKey(key)) {
// 重映射函数计算出的新值为空,并且当前map包含当前key,此时删除该key
// something to remove
remove(key);
return null;
} else {
// 当前map不包含当前key,则什么都不用做
// nothing to do. Leave things as they were.
return null;
}
} else {
// 计算出的新值不为空,则新增或者替换调原有映射
// add or replace old mapping
put(key, newValue);
// 返回新值
return newValue;
}
}
26. default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction)
如果指定的键未关联到一个值,或者关联到null
,则把该键关联到给定的非空值。否则,用指定的重映射函数计算出的结果来替换该键关联到的值,如果计算出的结果为null
则删除该键。当将一个键的多个映射值组合到一起时,可以使用此方法。
例如,创建或拼接一个String
类型的msg
到值映射:
map.merge(key, msg, String::concat)
如果函数返回null
,则当前映射会被删除。如果函数本身抛出了(不受检查)异常,则当前方法会将异常重新抛出,并且当前异常保持不变。
该默认实现等同于对该map执行如下步骤的操作,然后返回当前值或者null
(如果不存在):
V oldValue = map.get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if (newValue == null)
map.remove(key);
else
map.put(key, newValue);
默认实现不保证此方法的同步或原子性。任何提供原子性保证的实现都必须重写此方法并记录其并发属性。特别是,子接口java.util.concurrent.ConcurrentMap
的所有实现都必须记录该函数是否仅在该值不存在时以原子方式应用一次。
源码如下:
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
// 执行非空校验
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value);
// 先获取到指定键关联到的旧值
V oldValue = get(key);
// 如果旧值为空,则新值就取参数列表中指定的值value
// 否则,使用旧值和参数中给定的值value执行参数中指定的重映射函数 计算出新值
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
// 新值为空,则删除指定的键
remove(key);
} else {
// 新值不为空,则将该新值映射到指定的键上
put(key, newValue);
}
// 返回新值
return newValue;
}