前言
书接上文,上一篇中对 Set 接口的最终实现类 TreeSet 进行了介绍与分析,本篇开始将对 Map 接口进行分析。还是先来看下数据结构总继承关系图与 Map 部分的继承关系图:
可以看到 Map 与 Collection 数据结构本身是没有关系的,但是上一个对于 Set 接口的介绍篇章中可以看到,无论是实现了 Collection 特性的 HashSet 还是 TreeSet 其实都只是在操作 HashMap 和 TreeMap 而已,所以可以认为这是一种桥接模式,Map 与 Collection 完全解耦,但是却提供了实现 Collection 各种能力的方式。
还是首先来看顶端接口 Map
/**
* map 的原本翻译为映射,为了避免歧义,以下 map 作为数据结构出现时不翻译,作为键值映射出现时翻译为简直映射,
* 而 entry 则翻译成条目,只有在源码中写明 key-value pair 的地方才会翻译成键值对,view 则翻译成视图,这
* 样翻译是最精确的
*
* 一个映射键与值关系的对象。一个 map 不能包含相同的键,每个键可以映射至多一个值。
*
* 这个接口取代了 Dicionary 类的位置,它(Dictionary)完全是一个抽象类而不是一个接口。
*
* 映射接口提供三种数据结构试图,这允许一个映射的内容可以是一个 keys set 的视图,值的数据结
* 构集,或者键值映射的 set。一个映射的顺序被定义为与映射数据结构视图的串行迭代器中返回的
* 元素的顺序相同。一些映射实现类,像是 TreeMap 类,对它们的顺序做出了特定的保证;另外一
* 些,像是 HashMap 类,则没有。
*
* 注意:如果可变的对象用作 map 的 key 时需要特别注意。当一堆对象是 map 的键时,如果它被以
* 影响了 equals 比较行为的方式被更改了,map 是不会发现的。这种禁令的一种特殊情况是 map
* 不可能将它自己作为键。当 map 可以将自己作为值时,建议提高警惕:equals 和 hashCode 方
* 法不再在这个 map 中被完好的定义。
*
* 所有一般目的的 map 实现类应该提供两种“标准”的构造器:一个是空(没有参数)的构造器,构建一
* 个空的 map,还有一种是带有一个 Map 类型参数的构造器,构建一个与参数的映射关系完全相同
* 的新 map。其实,后一种构造器允许用户来拷贝任何 map,产生一个与需要的类型相同的 map。没
* 有方式可以强制执行这种建议(因为接口不能包含构造器)但是所有 JDK 中一般目的的 map 实现类
* 都遵从这种建议。
*
* “销毁”方法包含在这个接口中,意味着,对于改动 map 的方法,如果 map 不支持这种操作,就会
* 抛出 UnsupportedOperationException。如果是这种情况,这些方法可以,但不是强制的,如果
* 调用在 map 上没有副作用的话,抛出一个 UnsupportedOperationException。比如,在一个不
* 可改动的 map 上调用 {@link #putAll(Map)} 方法可能,但不是强制的,在 map 的映射要
* 去“叠加”空时候抛出异常。
*
* 一些 map 实现类它们包含的对键与值有限制。比如,一些实现类禁止 null 键和值,还有一些对于
* 它们的键类型有限制。试图插入一个不合规的键或者值抛出一个 unchecked 异常,典型的是
* NullPointerException 或者 ClassCastException。试图查询一个不合规的键或者值可能跑出
* 一个异常,或者它简单的返回 false;一些实现类将显示前者的行为而一些则会显示后者的。更一般
* 地说,假设在一个结束时不会返回一个不合规元素结果的不合规的键或者值上的操作会抛出一个异常或
* 者它可能成功,这是实现类可以选择的。类似的异常在这个接口规范中被标记为“可选的”。
*
* 许多 Collections 框架接口中的方法被从 {@link Object#equals(Object) equals} 方法
* 角度定义。比如,{@link #containsKey(Object) containsKey(Object key)} 方法这样描
* 述:“当且仅当这个 map 包含一个 key 为 k 的遵循
* (key==null ? k==null : key.equals(k))
* 的映射时候返回 true。”这个规范不应该被理解成有这样的隐藏含义:用一个不为 null 的参数键
* 调用 Map.containsKey 将导致 key.equals(k) 被任何键 k 调用。实现类可以自由的优化,从
* 而调用 equals,比如,首先比较两个键的哈希值。({@link Object#hashCode()} 规范保证了
* 两个 hash codes 不相等的对象不可能 equal。)更一般化地说,各种集合框架接口的实现类可以
* 在认为合适的时候自由地利用底层的指定行为的 {@link Object} 的方法。
*
* 一些执行 map 递归遍历的映射操作可能会在 map 直接或者间接的包含自身时候失败,伴随一个与自
* 我参照实例相关的异常。这包括 {@code clone()}, {@code equals()}, {@code
* hashCode()} and {@code toString()} 方法。实现类可以有选择的解决自我参照情景,但是,
* 大部分现有的实现类不那么做。
*/
public interface Map<K,V> {
// 查询操作
/**
* 返回 map 中的键值映射对数量。如果 map 中包含的元素笔 Integer.MAX_VALUE 多,返回
* Integer.MAX_VALUE。
*/
int size();
/**
* 如果当前 map 中不包含任何键值映射关系,则返回 true。
*/
boolean isEmpty();
/**
* 如果当前 map 包含指定键的键值对,则返回 true。更正式地说,当且仅当这个 map 包含类
* 似于 (key==null ? k==null : key.equals(k)) 这样的 k 作为 key 时返回 true。
* (最多只能有一个这样的映射。)
*/
boolean containsKey(Object key);
/**
* 如果当前 map 映射了一个或多个键在指定值上。更正式的说,当且仅当当前 map 包含至少一
* 个对于值 v 来说有类似 (value==null ? v==null : value.equals(v)) 的映射。这个
* 操作的时间对于大部分 Map 接口的实现类来说将大致与 map 的长度成线性关系。
*/
boolean containsValue(Object value);
/**
* 返回指定 key 映射的 值,或者如果当前 map 不包含键对应的映射则返回 {@code null}。
*
* 更正式地说,如果 map 包含一个类似于 {@code (key==null ? k==null :
* key.equals(k))} 从 key 到 value 的映射,那么这个方法返回 {@code v};否则它返
* 回 {@code null}。(至多只会存在一个这样的映射。)
*
* 如果 map 允许 null 值,那么一个 {@code null} 的返回值对于表明 map 中不包含 key
* 的映射就是不必要的了;它也有可能是 map 明确有键映射到 {@code null}。
* {@link#containsKey containsKey} 操作可以用来区分这两种情况。
*/
V get(Object key);
// 修改操作
/**
* 将当前 map 中的指定值与指定键关联(可选操作)。如果 map 之前包含了一个键的映射,旧
* 值将被指定值替代。(当且仅当一个 map m 对于 k 键包含类似于 {@link
* #containsKey(Object) m.containsKey(k)} 返回 true 的关系。)
*/
V put(K key, V value);
/**
* 从 map 中移除一个键的映射关系,如果它存在的话(可选操作)。更正式地说,如果这个 map
* 包含一个以 k 为键,v 为值的类似于 (key==null ? k==null : key.equals(k)) 映
* 射关系,这个映射会被移除。(map 值能包含至多一种这样的映射。)
*
* 返回 map 中之前与这个键关联的值,或者如果 map 中不包含这个键对应的映射则返回
* null。
*
* 如果 map 允许 null 值,那么一个 {@code null} 的返回值对于表明 map 中不包含 key
* 的映射就是不必要的了;它也有可能是 map 明确有键映射到 {@code null}。
*
* 一旦调用返回后 map 将不再包含指定的键。
*/
V remove(Object key);
// 扩展操作
/**
* 从指定的 map 中拷贝所有的映射到当前 map 中(可选操作)。这个调用的效果和在当前 map
* 中对指定 map 中一个一个键值对调用 {@link #put(Object,Object) put(k, v)} 的效
* 果是一样的。如果指定 map 在操作过程中被改动了,这种操作行为就是不可定义的。
*/
void putAll(Map<? extends K, ? extends V> m);
/**
* 移除当前 map 所有的键值对映射(可选操作)。调用后 map 将会为空。
*/
void clear();
// 视图
/**
* 返回一个包含当前 map 中所有键的 {@link Set} 视图。set 是被 map 支持的,所以对于
* map 的修改也会影响到 set,反之亦然。如果在一个覆盖 set 的迭代操作执行过程中 map 被
* 修改了(以通过串行迭代器自己的 remove 操作之外的方式),迭代操作的结果是不可定义的。
* set 支持元素移除,它对应移除 map 对应的键值对映射,通过 Iterator.remove,
* Set.remove,removeAll,retainAll 和 clear 操作。它不支持 add 或者 addAll 操
* 作。
*/
Set<K> keySet();
/**
* 返回一个当前 map 中包含的值的 {@link Collection} 视图。这个 collection 是被 map 支持的,所
* 以对于 map 的修改也会影响到 collection,反之亦然。如果在一个覆盖 collection 的迭代操作执行过
* 程中 map 被修改了(以通过串行迭代器自己的 remove 操作之外的方式),迭代操作的结果是不可定义的。
* collection 支持元素移除,它对应移除 map 对应的键值对映射,通过 Iterator.remove,
* Collection.remove,removeAll,retainAll 和 clear 操作。它不支持 add 或者 addAll 操作。
*/
Collection<V> values();
/**
* 返回一个当前 map 中包含的映射键值对的 {@link Set} 视图。这个 set 被 map 支持,所以对于 map
* 的改动也会影响 set,反之亦然。如果在一个覆盖 collection 的迭代操作执行过程中 map 被修改了(以通
* 过串行迭代器自己的 remove 操作之外的方式,或者对一个串行迭代器返回的 map entry 进行 setValue
* 操作),迭代操作就是不可定义的。set 支持元素移除,它对应移除 map 对应的键值对映射,通过
* Iterator.remove,Set.remove,removeAll,retainAll 和 clear 操作。它不支持 add 或者
* addAll 操作。
*/
Set<Map.Entry<K, V>> entrySet();
/**
* 一个 map 条目(键值对)。Map.entrySet 方法返回 map 的一个 collection 视图,它的元素就是这个类。
* 唯一包含一个引用到一个 map 条目的方式是通过迭代这个 collection 视图。这些 Map.Entry 对象仅仅为了
* 持续迭代操作存在;更正式地说,如果在条目被迭代器返回后,支持的 map 被修改了,那么一个 map 条目的行为
* 就是不可定义的,除非通过 map 条目的 setValue 来操作。
*
* 条目接口
*/
interface Entry<K,V> {
/**
* 返回这个条目对应的键。
*/
K getKey();
/**
* 返回这个条目对应的值。如果映射被从支持的 map 中移除了(通过迭代器的 remove 操作),这个调用的结
* 果就是不可定义的。
*/
V getValue();
/**
* 用指定值替换这个条目对应的值(可选操作)。(通过 map 写。)如果映射关系已经从 map 中移除(通过
* 迭代器的 remove 操作)了,那么这个调用行为是不可定义的。
*/
V setValue(V value);
/**比较当前条目与指定对象的相等性。如果给定对象也是一个 map 条目并且两个条目代表了相同的映射关系则
* 返回 true。更正式地说,两个条目 e1 和 e2 如果满足<pre>
* (e1.getKey()==null ?
* e2.getKey()==null : e1.getKey().equals(e2.getKey())) &&
* (e1.getValue()==null ?
* e2.getValue()==null : e1.getValue().equals(e2.getValue()))
* </pre>
* 就代表它们是相同的映射。这保证了 Map.Entry 接口的不同实现类都能通过 equals 方法正确地工作。
*/
boolean equals(Object o);
/**
* 返回当前 map 条目的 hash 值。一个 map 条目 e 的 hash 值定义为: <pre>
* (e.getKey()==null ? 0 : e.getKey().hashCode()) ^
* (e.getValue()==null ? 0 : e.getValue().hashCode())
* </pre>
* 这保证了对于任意两个条目 e1 和 e2,e1.equals(e2) 隐式的包含了
* e1.hashCode()==e2.hashCode() ,就像 Object.hashCode 的普遍规约那样。
*/
int hashCode();
/**
* 返回一个通过键的自然顺序比较 {@link Map.Entry} 的 comparator。
*/
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());
}
/**
* 返回一个通过值的自然顺序比较 {@link Map.Entry} 的 comparator。
*/
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());
}
/**
* 返回一个使用给定 {@link Comparator} 作为键的顺序来比较 {@link Map.Entry} 的 comparator。
*/
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()); //函数式调用,c1,c2 都是 map 的条目,比较它们的键
}
/**
* 返回一个使用给定 {@link Comparator} 作为值的顺序来比较 {@link Map.Entry} 的 comparator。
*/
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()); //函数式调用,c1,c2 都是 map 的条目,比较它们的值
}
}
// 比较和哈希
/**
* 比较给定对象与当前 map 的相等性。如果给定对象也是一个 map 并且两个 map 提供了相同的键值映射则返回
* true。更正式地说,两个 maps m1 和 m2 如果满足
* <tt>m1.entrySet().equals(m2.entrySet())</tt>
* 则表示它们是相同的。这保证了在 Map 接口不同的实现类中 equals 方法都可以正常地工作
*/
boolean equals(Object o);
/**
* 返回当前 map 的 hash 值。一个 map 的 hash 值是由 map 的 entrySet 视图中的每个条目的 hash 值累
* 加得到的。这保证了对于任意两个 maps m1 和 m2,m1.equals(m2) 隐式的包含了 m1.hashCode() ==
* m2.hashCode(),就像 {@link Object#hashCode} 普遍规约中要求的。
*/
int hashCode();
// 默认类型的方法
/**
* 返回给定键映射的值,或者如果这个 map 中不包含这个键的映射关系,则返回 {@code defaultValue}
* Returns the value to which the specified key is mapped, or
* {@code defaultValue} if this map contains no mapping for the key.
*
* 实现细节
* 默认实现类不保证线程安全或者这个方法属性的原子性。任何提供原子性保证的实现类必须重写这个方法和标记它
* 的并发属性。
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
/**
* 为每个当前 map 中的条目执行指定的消费动作指导所有的条目都被处理或者消费动作抛出一个异常。除非这个实现
* 那类指定其他特定的,消费动作按照条目 set 迭代动作来执行(如果一个迭代顺序是特定的。)消费动作引发的异
* 常将传递到调用者。
*
* 实现细节
* 默认实现等价于,对于当前 {@code map}:
* <pre> {@code
* for (Map.Entry<K, V> entry : map.entrySet())
* action.accept(entry.getKey(), entry.getValue());
* }</pre>
*
* 默认实现类不保证线程安全或者这个方法属性的原子性。任何提供原子性保证的实现类必须重写这个方法和标记它
* 的并发属性。
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
//通过 for 循环 entrySet 中的条目
K k;
V v;
try {
k = entry.getKey(); //缓存条目的键
v = entry.getValue(); //缓存条目的值
} catch(IllegalStateException ise) {
/**这通常意味着条目不再存在在 map 中**/
throw new ConcurrentModificationException(ise);
}
action.accept(k, v); //逐个消费键,值
}
}
/**
* 在条目上调用指定 function 替换每个条目的值,直到所有条目都被处理了或者 function 抛出一个异常。异常
* 将传递到调用者
*
* 实现细节
* 默认实现类等价于,对于当前 {@code map}
* <pre> {@code
* for (Map.Entry<K, V> entry : map.entrySet())
* entry.setValue(function.apply(entry.getKey(), entry.getValue()));
* }</pre>
*
* 默认实现类不保证线程安全或者这个方法属性的原子性。任何提供原子性保证的实现类必须重写这个方法和标记它
* 的并发属性。
*/
default void replaceAll(BiFunction<? super K, ?