Map接口是所有Java具体Map需要实现的接口,比如HashMap。了解Map接口设计有利于理解各种具体Map如何实现。
一、概述
将键映射到值的对象。映射不能包含重复的键;每个键最多只能映射到一个值。这个接口代替了Dictionary类,后者是一个完全抽象的类,而不是一个接口。
Map接口提供了三个集合视图,它允许将映射的内容视为键集、值集或键值映射集。映射的顺序定义为映射集合视图上的迭代器返回元素的顺序。一些map实现,比如TreeMap类,对它们的顺序做了特定的保证;其他类,如HashMap类,则不是这样。
注意:如果使用可变对象作为map键,则必须非常小心。如果对象的值以影响equals比较的方式更改,而对象是map中的键,则不指定map的行为。这种禁止的一个特殊情况是,不允许map将自己包含为键。虽然允许map本身作为一个值,但是要特别小心:equals和hashCode方法在这样的map上不再有很好的定义。
所有通用Map实现类都应该提供两个“标准”构造函数:一个是void(无参数)构造函数,它创建一个空映射;另一个是构造函数,它只有一个类型为Map的参数,它创建的新map具有与其参数相同的键值映射。实际上,后一个构造函数允许用户复制任何映射,生成所需类的等效map。没有办法强制执行这个建议(因为接口不能包含构造函数),但是JDK中的所有通用map实现都遵从这个建议。
如果此Map不支持该操作,则此接口中包含的“破坏性”方法(即修改它们操作的Map的方法)被指定为抛出UnsupportedOperationException。 如果是这种情况,如果调用对Map没有影响,这些方法可能(但不是必须)抛出UnsupportedOperationException。
一些Map实现对它们可能包含的键和值有限制。例如,有些实现禁止空键和值,有些实现对键的类型有限制。试图插入不符合条件的键或值会引发未检查的异常,通常是NullPointerException或ClassCastException。试图查询不符合条件的键或值是否存在时,可能会抛出异常,或者返回false;有些实现将显示前一种行为,有些将显示后一种行为。更一般地说,尝试对不符合条件的键或值进行操作,如果该操作的完成不会导致将不符合条件的元素插入到映射中,则可以根据实现的选择抛出异常或成功执行。在这个接口的规范中,这些异常被标记为“可选”。
集合框架接口中的许多方法都是根据equals方法定义的。例如,containsKey(Object key)方法的规范说:“当且仅当此Map包含键k的映射时,返回true。(key == null ? k == null : key.equals(k))”。此规范应该不被解释为意味着使用非空参数键,调用Map.containsKey将导致key.equals(k)为任何键k调用。实现类可以自由地实现优化,从而避免equals调用,例如,首先比较两个键的哈希码。hashCode()规范保证两个具有不等哈希码的对象不能相等)。更一般地,各种集合框架接口的实现可以在实现者认为合适的地方自由地利用底层Object方法的指定行为。
执行Map递归遍历的某些Map操作可能会失败抛出异常,因为Map直接或间接包含自身的自引用实例。 这包括clone(), equals(), hashCode()和toString()方法。 实现可以可选地处理自引用场景,但是大多数当前实现不这样做。
这个接口是java集合框架的成员。
二、结构
先来看IDE中的Map方法结构图,下面分别是未展开和展开后的方法等分布。
从上图中不难看出,Map接口中包含了另外一个接口----Entry,其它的方法则包含了对Map各种操作定义。如果换做是我们自己实现这样的一个数据结构操作公共接口,你会想到什么呢?增删查改当然少不了。遍历整个数据结构是不是也是需要的?实际上还包括获取数据结构的一些基本信息也是需要的。
从接口定义的方法中确实也发现了put、remove、get和replace这些增删查改的方法。具体一点:
-
向Map中增加数据包含了put、putAll和putIfAbsent;
-
从Map中删除数据包含了remove和clear;
-
在Map中查找数据包含了containsKey、containsValue、get和getOrDefault;
-
在Map中修改数据包含了replace和replaceAll;
-
迭代Map包括了方法forEach,以及返回三种集合数据视图的方法keySet、values和entrySet;
-
获取Map信息的方法,包含了size和isEmpty;
-
最后是JDK 1.8新增的综合操作数据方法,涉及merge、compute、computeIfAbsent和computeIfPresent。
很明显,每种Map的具体实现将键值对抽象成Entry,Entry接口定义了键值对的公用操作:
-
getKey
-
getValue、setValue
-
comparingByKey、comparingByValue
三、详细设计
下面是Map接口每个方法的作用,这些方法共同构成了Map接口的详细设计,所有其实现类都要实现这些接口方法。
3.0 预备
结合代码中的注释不难知道BiFunction、Function接口的作用,它们都是JDK 1.8引入的。
3.0.1 BiFunction
BiFunction表示接受两个参数并生成结果的函数。
@FunctionalInterface
public interface BiFunction<T, U, R> {
/**
* Applies this function to the given arguments.
* 将此函数应用于给定的参数。
* @param t the first function argument
* @param u the second function argument
* @return the function result
*/
R apply(T t, U u);
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
* 返回一个复合函数,该函数首先将此函数应用于其输入,然后将after函数应用于结果。
* 如果任一函数的求值引发异常,则将异常转发给组合函数的调用方。
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*/
default <V> BiFunction<T, U, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t, U u) -> after.apply(apply(t, u));
}
}
3.0.2 Function
再来看上面提到的Function接口,Function接口表示接受一个参数并生成结果的函数。
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
* 将此函数应用于给定的参数。
* @param t the function argument
* @return the function result
*/
R apply(T t);
/**
* Returns a composed function that first applies the {@code before}
* function to its input, and then applies this function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
* 返回一个复合函数,该函数首先将before函数应用于其输入,然后将此函数应用于结果。
* 如果任一函数的求值引发异常,则将异常转发给组合函数的调用方。
* @param <V> the type of input to the {@code before} function, and to the
* composed function
* @param before the function to apply before this function is applied
* @return a composed function that first applies the {@code before}
* function and then applies this function
* @throws NullPointerException if before is null
*
* @see #andThen(Function)
*/
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
/**
* Returns a composed function that first applies this function to
* its input, and then applies the {@code after} function to the result.
* If evaluation of either function throws an exception, it is relayed to
* the caller of the composed function.
* 返回一个复合函数,该函数首先将此函数应用于其输入,然后将after函数应用于结果。
* 如果任一函数的求值引发异常,则将异常转发给组合函数的调用方。
* @param <V> the type of output of the {@code after} function, and of the
* composed function
* @param after the function to apply after this function is applied
* @return a composed function that first applies this function and then
* applies the {@code after} function
* @throws NullPointerException if after is null
*
* @see #compose(Function)
*/
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
* 返回一个总是返回其输入参数的函数。
* @param <T> the type of the input and output objects to the function
* @return a function that always returns its input argument
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
3.0.3 BiConsumer
表示接受两个输入参数但不返回结果的操作。
@FunctionalInterface
public interface BiConsumer<T, U> {
/**
* Performs this operation on the given arguments.
* 对给定的参数执行此操作。
* @param t the first input argument
* @param u the second input argument
*/
void accept(T t, U u);
/**
* Returns a composed {@code BiConsumer} that performs, in sequence, this
* operation followed by the {@code after} operation. If performing either
* operation throws an exception, it is relayed to the caller of the
* composed operation. If performing this operation throws an exception,
* the {@code after} operation will not be performed.
* 返回一个组合的BiConsumer,按顺序执行此操作,然后执行after操作。
* 如果执行任一操作抛出异常,它将被转发到组合操作的调用者。如果执行此操作会引发异常,则不会执行after操作。
* @param after the operation to perform after this operation
* @return a composed {@code BiConsumer} that performs in sequence this
* operation followed by the {@code after} operation
* @throws NullPointerException if {@code after} is null
*/
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
3.0.4 Entry
Entry接口中的方法解释,都已经注释在方法上面了,一目了然。为了缩减篇幅,已经将代码中的英文注释和参数说明、返回值都去掉了。
interface Entry<K,V> {
/**
* 返回与此条目对应的键。
*/
K getKey();
/**
* 返回与此条目对应的值。如果映射已经从备份map中删除(通过迭代器的remove操作),
* 则此调用的结果是未定义的。
*/
V getValue();
/**
* 用指定的值替换此条目对应的值(可选操作)。如果映射已经从map中删除(通过迭代器的remove操作),
* 则此调用的行为是未定义的。
*/
V setValue(V value);
/**
* 比较指定的对象与此条目是否相等。
*/
boolean equals(Object o);
/**
* 返回此映射项的散列代码值。 map条目e的哈希码定义为:
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())
*/
int hashCode();
/**
* 返回一个比较器,用于在键上按自然顺序比较Map.Entry。
* 返回的比较器是可序列化的,并在将条目与空键进行比较时抛出NullPointerException。
* @since 1.8
*/
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。
* 返回的比较器是可序列化的,并在比较具有空值的条目时抛出NullPointerException。
* @since 1.8
*/
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。
* 如果指定的比较器也是可序列化的,则返回的比较器是可序列化的。
* @since 1.8
*/
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。
* 如果指定的比较器也是可序列化的,则返回的比较器是可序列化的。
* @since 1.8
*/
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());
}
}
3.1 增加数据
向Map中增加数据包含了put、putAll和putIfAbsent。
3.1.1 put
V put(K key, V value);
将指定值与此Map中的指定键关联(可选操作)。如果Map已经包含键的映射,则旧值将被指定的值替换。(map m包含键k的映射,当且仅当m.containsKey(k)返回true)。
3.1.2 putAll
void putAll(Map<? extends K, ? extends V> m);
这是一个批量添加接口方法,实际是参数中的Map逐个调用put方法进行添加。如果在批量添加操作进行时修改了指定的Map,则此操作的行为未定义。
3.1.3 putIfAbsent
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
这个方法是JDK 1.8引入的。从源码可以知道,首先使用键key获取值v,再去判断值v是否为空,如果为空则使用方法参数的value进行替换null值,否则直接返回旧值v。
具体地,如果指定的键尚未与值关联(或映射到null),则将其与给定值关联并返回null,否则返回当前值。
3.2 删除数据
从Map中删除数据包含了remove和clear。
3.2.1 remove
V remove(Object key);
如果键存在,则从该Map中删除该键的映射(可选操作)。更正式地说,如果这个Map包含一个从键k到值v的映射,那么(key == null ? k == null : key.equals(k)),该映射被删除。(Map最多只能包含一个这样的映射)。
返回此映射先前关联键的值,如果Map不包含键的映射,则返回null。
如果这个Map允许null值,那么null的返回值不一定表示Map不包含键的映射;Map也可能显式地将键映射为null。
一旦调用返回,Map将不包含指定键的映射。
3.2.2 clear
void clear();
从该Map中删除所有映射(可选操作)。此调用返回后,Map将为空。
3.3 查找数据
在Map中查找数据包含了containsKey、containsValue、get和getOrDefault。
3.3.1 containsKey
boolean containsKey(Object key);
如果此Map包含指定键的映射,则返回true。更正式地说,返回true当且仅当此Map包含键k的映射(key == null ? k == null : key.equals(k))。(这样的映射最多只能有一个)。
3.3.2 containsValue
boolean containsValue(Object value);
如果该Map映射到指定值的一个或多个键,则返回true。更正式地说,返回true当且仅当此Map包含值v的至少一个映射(value == null ? v == null: value.equals (v))。对于map接口的大多数实现,此操作Map中包含映射数量和时间成正比。
3.3.3 get
V get(Object key);
返回指定键映射到的值,如果该Map不包含键的映射,则返回null。
更正式地说,如果这个Map包含从键k到值v的映射,那么(key == null ? k == null : key.equals(k)),则该方法返回v;否则返回null。(这样的映射最多只能有一个)。
如果这个Map允许null值,那么null的返回值不一定表示Map不包含键的映射;映射也可能显式地将键映射为null。containsKey操作可用于区分这两种情况。
3.3.4 getOrDefault
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
同样这个方法自JDK 1.8引入,从上面的源码分析可知,如果键key对应的值不是null,则直接返回对应的v,否则检查Map是否包含key,如果包含则返回null,说明key对应的值就是null,不存在上面的情况则返回defaultValue。
更具体地说,返回指定键映射到的值,如果该Map不包含该键的映射,则返回defaultValue。
3.4 修改数据
在Map中修改数据包含了replace和replaceAll。replace有两个版本,一个为(V replace(K key, V value) ),另一个为(boolean replace(K key, V oldValue, V newValue))。
3.4.1 replace
下面两个方法都是从JDK 1.8引入的。
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
以上方法首先判断key是否存在,存在的话就使用put进行更新其值为新的value,并返回旧的curValue。如果不存在key,则返回null。
default boolean replace(K key, V oldValue, V newValue) {
Object curValue = get(key);
if (!Objects.equals(curValue, oldValue) ||
(curValue == null && !containsKey(key))) {
return false;
}
put(key, newValue);
return true;
}
这个方法首先通过key得到对应的curValue,接着当curValue和oldValue不相等或者curValue为空且当前Map不包含此key,就返回false,替换失败。否则再使用put进行替换操作,执行完put方法后返回true。
这两个方法都是仅当当前映射到指定值时,才替换指定键的项。
3.4.2 replaceAll
用调用该条目上的给定函数的结果替换每个条目的值,直到所有条目都被处理或该函数抛出异常为止。函数抛出的异常被转发给调用者。
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
Objects.requireNonNull(function);
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 thrown from function is not a cme.
v = function.apply(k, v);
try {
entry.setValue(v);
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
}
}
阅读源码可以看到首先遍历整个Map,然后获取对应的key(k)和value(v),接着应用BiFunction的apply函数返回新的v,最后使用新的v更新对应的entry。
3.5 迭代
迭代Map包括了方法forEach,以及返回三种集合数据视图的方法keySet、values和entrySet。
3.5.1 forEach
对此Map中的每个条目执行给定操作,直到处理完所有条目或操作引发异常。除非实现类另有指定,否则将按入口集迭代的顺序执行操作(如果指定了迭代顺序)。操作抛出的异常将转发到调用者。
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
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);
}
action.accept(k, v);
}
}
3.5.2 keySet
返回此映射中包含的键的Set视图。该集合由map支持,因此对map的更改将反映在集合中,反之亦然。如果在对集合进行迭代时修改了map(除了通过迭代器自己的remove操作),迭代的结果是未定义的。该集合支持元素删除,它通过Iterator.remove ,Set.remove,removeAll,retainAll和clear从map中删除相应的映射。它不支持add或addAll操作。
Set<K> keySet();
3.5.3 values
返回此Map中包含的值的Collection视图。集合由map支持,因此map的更改反映在集合中,反之亦然。如果在对集合进行迭代时修改map(除了通过迭代器自己的remove操作之外),则迭代的结果是未定义的。该集合支持元素删除,它通过Iterator.remove ,Collection.remove,removeAll,retainAll和clear从map中删除相应的映射。它不支持add或addAll操作。
Collection<V> values();
3.5.4 entrySet
返回此Map中包含的映射的Set视图。该集合由map支持,因此对map的更改将反映在集合中,反之亦然。如果在对集合进行迭代时修改了map(除非通过迭代器自己的remove操作,或者对迭代器返回的映射条目执行setValue操作)迭代的结果是未定义的。 该集合支持元素删除,它通过Iterator.remove ,Set.remove,removeAll,retainAll和clear从map中删除相应的映射。它不支持add或addAll操作。
Set<Map.Entry<K, V>> entrySet();
3.6 获取信息
获取Map信息的方法,包含了size和isEmpty。
3.6.1 size
返回此Map中的键值映射的数量。如果Map包含元素数量大于Integer.MAX_VALUE,返回Integer.MAX_VALUE。
int size();
3.6.2 isEmpty
如果此Map不包含键值映射,则返回true。
boolean isEmpty();
3.7 新增综合操作
以下接口方法都是JDK 1.8新增的综合操作数据方法,涉及merge、compute、computeIfAbsent和computeIfPresent。
3.7.1 merge
如果指定的键尚未与值关联或与null关联,则将其与给定的非空值关联。否则,将关联的值替换为给定映射函数的结果,如果结果是null,则删除。当组合一个键的多个映射值时,此方法可能有用。
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);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
merge这个单词本身就有合并的意思,所以这个函数的作用就是合并键值对。从源码分析可知,先获得key对应的oldValue,如果为空,newValue直接等于形参送入的value值。否则,应用映射函数返回的结果(它使用oldValue和value作为形参)作为newValue,比如我们实现apply函数为简单的拼接oldValue和value字符串。接着执行代码,会判断newValue是否为空,如果为空则从Map中删除这个键值对,不为空则使用put方法进行更新。最后返回newValue。
3.7.2 compute
尝试计算指定键及其当前映射值的映射(如果没有当前映射,则为null)。
default V compute(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue = get(key);
V newValue = remappingFunction.apply(key, oldValue);
if (newValue == null) {
// delete mapping
if (oldValue != null || containsKey(key)) {
// something to remove
remove(key);
return null;
} else {
// nothing to do. Leave things as they were.
return null;
}
} else {
// add or replace old mapping
put(key, newValue);
return newValue;
}
}
阅读源码容易知道,首先我们根据compute方法形参key获得对应的oldValue,接着调用重新映射函数BiFunction apply(它使用key和oldValue作为形参),获得新的newValue,然后判断newValue是否为空,如果为空,并且map中包含key,则从map中删除对应的键值对,并返回null。否则什么都不做,直接返回null。另外如果newValue不为空,则调用put进行添加或更新。
newValue不为空,则调用put进行添加或更新。
3.7.3 computeIfAbsent
如果指定的键尚未与值关联(或映射到null),则尝试使用给定的映射函数计算其值,并将其输入此映射,除了值为null。
default V computeIfAbsent(K key,
Function<? super K, ? extends V> mappingFunction) {
Objects.requireNonNull(mappingFunction);
V v;
if ((v = get(key)) == null) {
V newValue;
if ((newValue = mappingFunction.apply(key)) != null) {
put(key, newValue);
return newValue;
}
}
return v;
}
代码中首先获得了key对应的value,当value为null时,通过映射函数计算newValue,当newValue不为null时,使用put进行添加或更新。
3.7.4 computeIfPresent
如果指定键的值存在且为非null,则尝试在给定键及其当前映射值的情况下计算新映射。
default V computeIfPresent(K key,
BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
V oldValue;
if ((oldValue = get(key)) != null) {
V newValue = remappingFunction.apply(key, oldValue);
if (newValue != null) {
put(key, newValue);
return newValue;
} else {
remove(key);
return null;
}
} else {
return null;
}
}
源码中,首先通过key获得oldValue,其不为null时,使用提供的映射函数(它以key和oldValue作为形参)获得newValue,newValue不为null时,调用put进行更新。否者newValue为null,直接从map中删除键值对。
3.7.5 Demo
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
public class TestMap {
private static Map<String, String> map = new HashMap<>();
public static void main(String[] args) {
// 向map中添加键值对
map.put("1", "1");
map.put("2", null);
map.put("3", "3");
map.put("4", null);
map.put("5", "5");
map.put("6", null);
map.put("7", "7");
map.put("8", null);
map.put("9", "9");
map.put("10", null);
System.out.println("1 merge map=>" + map.toString());
map.merge("1", "1", String::concat);
map.merge("2", "2", String::concat);
System.out.println("2 merge map=>" + map.toString());
BiFunction<String, String, String> concatExt = (s, s2) -> s + "#" + s2;
System.out.println("3 merge map=>" + map.toString());
map.merge("3", "3", concatExt);
map.merge("4", "4", concatExt);
System.out.println("4 merge map=>" + map.toString());
System.out.println("1 compute map=>" + map.toString());
map.compute("5", concatExt);
map.compute("6", concatExt);
System.out.println("2 compute map=>" + map.toString());
Function<String, String> concatExt1 = s -> "@" + s;
System.out.println("1 computeIfAbsent map=>" + map.toString());
map.computeIfAbsent("7", concatExt1);
map.computeIfAbsent("8", concatExt1);
System.out.println("2 computeIfAbsent map=>" + map.toString());
System.out.println("1 computeIfPresent map=>" + map.toString());
map.computeIfPresent("9", concatExt);
map.computeIfPresent("10", concatExt);
System.out.println("2 computeIfPresent map=>" + map.toString());
}
}
运行结果:
1 merge map=>{1=1, 2=null, 3=3, 4=null, 5=5, 6=null, 7=7, 8=null, 9=9, 10=null}
2 merge map=>{1=11, 2=2, 3=3, 4=null, 5=5, 6=null, 7=7, 8=null, 9=9, 10=null}
3 merge map=>{1=11, 2=2, 3=3, 4=null, 5=5, 6=null, 7=7, 8=null, 9=9, 10=null}
4 merge map=>{1=11, 2=2, 3=3#3, 4=4, 5=5, 6=null, 7=7, 8=null, 9=9, 10=null}
1 compute map=>{1=11, 2=2, 3=3#3, 4=4, 5=5, 6=null, 7=7, 8=null, 9=9, 10=null}
2 compute map=>{1=11, 2=2, 3=3#3, 4=4, 5=5#5, 6=6#null, 7=7, 8=null, 9=9, 10=null}
1 computeIfAbsent map=>{1=11, 2=2, 3=3#3, 4=4, 5=5#5, 6=6#null, 7=7, 8=null, 9=9, 10=null}
2 computeIfAbsent map=>{1=11, 2=2, 3=3#3, 4=4, 5=5#5, 6=6#null, 7=7, 8=@8, 9=9, 10=null}
1 computeIfPresent map=>{1=11, 2=2, 3=3#3, 4=4, 5=5#5, 6=6#null, 7=7, 8=@8, 9=9, 10=null}
2 computeIfPresent map=>{1=11, 2=2, 3=3#3, 4=4, 5=5#5, 6=6#null, 7=7, 8=@8, 9=9#9, 10=null}
从结果不难分析出:
1.merge函数总是合并旧值和新值,如果旧值为null,就直接更新为新值;
2.compute函数重新计算value,重新映射函数以key和旧的value作为参数,返回新的value;
3.computeIfAbsent函数在值为null时更新为新值;
4.computeIfPresent函数更新值不为null的键值对。