前言
大家好,在之前的文章中,我们分析了 List 接口下的实现类 ArrayList 和 LinkedList 的源码。但是其中还有一个实现类 Vector 并没有说到,该类的实现与 ArrayList 基本相同,都是采用数组实现,区别就是其中的大量方法,如 add()、get() 等都是采用 synchronized 进行修饰,保证了线程安全。而 ArrayList 我们之前就分析过了,所以这里就不对 Vector 进行分析了,大家有兴趣可以自己看看源码。
下面我们该进入今天的主题了:来分析 Map 接口的源码。
“诶!你这跳转也太大了吧!Set 接口怎么还没分析就跳到 Map 了!”
别心急嘛,谁叫 HashSet 和 TreeSet 的内部实现都分别是由 HashMap 和 TreeMap 实现的呢?不先分析 Map 没办法呐… 好了,快进入正题吧。
概述
Java 中的 Map 接口和 Collection 接口一样,都是同一等级的根接口。Map 表示的是一个键(key)值(Value)对的映射,其中键是唯一的,每个键可以映射最多一个值。该接口取代了 Dictionary 抽象类。
需要注意的是,如果我们把可变对象作为 Map 的键,则必须要非常小心了,因为有可能会导致对象更改后查找不到对应的 value 了。这是因为,像 Map 的实现类,如 HashMap,是以 key 的哈希值来存储和查找键值对的(在后面的文章中会进行深深入分析),而一个可变对象在创建后其哈希值是可能被改变的。为了解决这种问题,我们最好是用 String、Integer 等不可变对象来作为 key,或者我们重写类的 hasCode() 、equals() 方法,用类的成员变量来进行计算。例如:
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (id != null ? !id.equals(person.id) : person.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
另外,我们虽然可以将 Map 作为 Map 的值,但是应避免将 Map 作为 Map 的键,因为 Map 比较复杂,比较难定义 hashCode() 与 equals() 方法。
Map 所有通用的实现类应该提供两个“标准”的构造函数:
- 一个无参数无返回值的空构造函数。
- 一个具有 Map 类型单个参数的构造函数,它创建一个具有参数相同键值的新 Map。
虽然没有强制要求这样做,但是我们要是自定义 Map 实现类时最好都按照这样来。
结构
上图是 Map 集合的整体 UML 图。在以后的文章,我们再来一个个分析 Map 接口下面的实现类、接口。
再来看看 Map 接口中都定义了一些什么方法:
其中,从 getOrDefault() 方法开始的方法,都是从 Java8 开始引入的新特性:默认方法。下面我们来开始从头开始一个一个分析。
源码分析
查询操作
/**
* 返回此 map 中键值映射的数量。如果 map 包含多于 Integer.MAX_VALUE 的元素,返回 Integer.MAX_VALUE。
*/
int size();
/**
* 如果此 map 中不包含键值映射,返回 true,否则返回 false。
*/
boolean isEmpty();
/**
* 如果此 map 的映射中包含指定的映射键,则返回 true。
*/
boolean containsKey(Object key);
/**
* 如果此 map 中将一个或多个键映射到指定的 value,则返回 true。
*/
boolean containsValue(Object value);
/**
* 返回指定键映射的值,如果此映射中不包含该键的映射,则返回 null。
*/
V get(Object key);
修改操作
/**
* 将指定的值于此 map 中指定的键相关联。
* 如果 map 中已经存在该键,那么将会替换值。
*/
V put(K key, V value);
/**
* 如果此 map 中存在指定 key 的映射,那么删除这个映射,并返回对于的值。
* 如果不存在指定 key 的映射,那么返回 null。(如果此 map 允许 null 值
* ,那么返回 null 并不一定表示该 map 不存在指定 key 的映射)
*/
V remove(Object key);
批量操作
/**
* 将指定 map 中的所有映射复制到此 map。
* 如果在操作过程中修改了指定的 map,则此操作的行为是未定义的。
*/
void putAll(Map<? extends K, ? extends V> m);
/**
* 从此 map 中删除所有的映射。
*/
void clear();
查看
/**
* 返回此 map 中包含所有键的 Set 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Set<K> keySet();
/**
* 返回此 map 中包含所有值的 Collection 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Collection.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Collection<V> values();
/**
* 返回此 map 中包含所有映射(Map.Entry)的 Set 集合。
* 该集合由 map 支持,因此对 map 的更改将反映在集合中,反之亦然。
* 如果在集合的迭代过程中修改了 map(除了用迭代器自己的删除操作,或
* 通过迭代器返回的映射中的 setValue 操作之外),迭代的结果是未定义的。
* 该集合支持删除元素,可以通过 Iteration.remove,Set.remove,removeAll,retainAll
* 和 clear 操作从 map 中删除相应的映射。它不支持 add 或 addAll 操作。
*/
Set<Map.Entry<K, V>> entrySet();
Entry 是 Map 接口当中的一个内部接口,表示的是一个键值对的映射。其源码如下:
/**
* map 条目(键值对)。通过 Map.entrySet() 方法可以获取在 Set 集合中其所有的映射条目。
* 因为是 Set 集合,所以该映射条目也是唯一的。要想获取映射条目的引用唯一的方法就是通过集合的迭代器。
* 这些 Map.Entry 对象仅在迭代期间有效。如果在迭代器返回条目之后修改了底层映射,
* 除了通过映射条目上的 setValue 操作,则映射条目的行为是不确定的。
*/
interface Entry<K,V> {
/**
* 返回与此条目想对应的键。
*/
K getKey();
/**
* 返回与此条目相对应的值。
* 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
*/
V getValue();
/**
* 用指定的值替换此条目相对应的值。(写入 map)
* 如果这个映射已经从底层映射中删除(通过迭代器的 remove 操作),则此调用结果未定义。
*/
V setValue(V value);
/**
* 将指定的对象与此条目进行比较以获得相等性。
* 如果给定的对象也是映射条目,并且两个条目表示相同的映射,则返回 true。
*/
boolean equals(Object o);
/**
* 返回此映射条目的哈希码。
*/
int hashCode();
// 下面的这几个都是从 Java8 开始提供的静态方法。关于什么是静态方法,可以看看下面提到的文章。
/**
* 返回一个以 key 的自然顺序进行比较的比较器。
* 返回的比较器是可序列化的,并在与为 null 的 key 比较时返回 NullPointerExcetion。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个以 value 的自然顺序进行比较的比较器。
* 返回的比较器是可序列化的,并在与为 null 的 value 比较时返回 NullPointerExcetion。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个使用指定的比较器比较 Map.Entry key 的比较器。
* 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
* 该方法是从 Java8 开始提供的。
*/
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());
}
/**
* 返回一个使用指定的比较器比较 Map.Entry value 的比较器。
* 如果指定的比较器是可序列化的,那么返回的比较器也可序列化。
* 该方法是从 Java8 开始提供的。
*/
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。
*/
boolean equals(Object o);
/**
* 返回此 map 的哈希码。map 的哈希码定义是 map 的 entrySet() 集合中每个条目的哈希码的总和。
*/
int hashCode();
Java 8 新增的默认方法
关于什么是默认方法,大家可以看这篇文章:Java 8 之接口中的默认方法与静态方法。
由于 Map 是接口,不可实例化,于是我们用 HashMap 为实例,演示下以下默认方法的使用。
首先,存储几个数据,后面的例子以这些数据为例来演示。
HashMap<Integer, String> map = new HashMap<>();
map.put(1, "a");
map.put(2, "b");
map.put(3, "c");
getOrDefault
/**
* 如果指定的 key 存在,则返回指定 key 对应的 value。
* 如果不存在则返回指定的默认 value 值。
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
例:
System.out.println(map.getOrDefault(1, "d"));// a
System.out.println(map.getOrDefault(4, "d"));// d
forEach
/**
* 遍历 Map 中所有的 Entry,对 key、value 进行处理。
*
* 这个方法的默认实现相当于:
* 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);
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);
}
}
例:
map.forEach((key, value) -> System.out.print(key + value));// 1a2b3c
replaceAll
/**
* 将 map 中每个 Entry 的 value 替换为给定的 value。
*
* 这个方法的默认实现相当于:
* 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);
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.replaceAll((key, value) -> "d" );
map.forEach((key, value) -> System.out.println(key + value));// 1d 2d 3d
putIfAbsent
/**
* 如果指定的 key 尚未与指定的 value 相关联(或映射到 null),
* 则将其指定的 value 相关联并返回 null,否则返回当前 value。
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
例:
System.out.println(map.putIfAbsent(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d
remove
/**
* 当指定的 key 映射到指定的 value 时,删除该 Entry。
*
* 该方法的默认实现相当于:
* 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) {
Object curValue = get(key);// 获取指定 key 的映射 value
if (!Objects.equals(curValue, value) ||// 比对指定的 value 与映射 value 是否是同一个,不是同一个直接返回 false
(curValue == null && !containsKey(key))) {// 当是同一个时(可能都为 null),再判断映射值是否为 null 并且 key 不存在,返回 false
return false;
}
remove(key);// 删除指定 Entry (抽象方法,由具体实现类实现删除逻辑)
return true;
}
例:
System.out.println(map.remove(1, "a"));// true
System.out.println(map.remove(4, null));// false
map.forEach((key, value) -> System.out.println(key + value));// 2b 3c
replace
/**
* 当指定的 key 和 value 是映射关系时,用 newValue 替换指定 value。
*
* 该方法的默认实现相当于:
* if (map.containsKey(key) && Objects.equals(map.get(key), value)) {
* map.put(key, newValue);
* return true;
* } else
* return false;
* }
*
* 如果 oldValue 为 null,则默认实现不会为不支持 null 值的映射抛出 NullPointerException,除非 newValue 也为 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;
}
例:
System.out.println(map.replace(1, "a", "d"));// true
System.out.println(map.replace(2, "c", "e"));// false
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c
/**
* 当指定 key 的映射 value 不为 null 或 key 存在时,给 key 替换指定的 value,并返回被替换的 value。否则返回 null。
*
* 该默认实现相当于:
* if (map.containsKey(key)) {
* return map.put(key, value);
* } else
* return null;
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
*/
default V replace(K key, V value) {
V curValue;
if (((curValue = get(key)) != null) || containsKey(key)) {
curValue = put(key, value);
}
return curValue;
}
例:
System.out.println(map.replace(1, "d"));// a
System.out.println(map.replace(4, "d"));// null
map.forEach((key, value) -> System.out.println(key + value));// 1d 2b 3c
coomputeIfAbsent
/**
* 如果指定的 key 尚未与 value 相关联(或映射到 null),
* 则尝试使用给定的映射函数计算其 value 并返回,当返回不是 null,则将其输入到此映射。
*
* 默认实现相当于:
* if (map.get(key) == null) {
* V newValue = mappingFunction.apply(key);
* if (newValue != null)
* map.put(key, newValue);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
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;
}
例:
System.out.println(map.computeIfAbsent(1, (key) -> "d"));// a
System.out.println(map.computeIfAbsent(4, (key) -> "d"));// d
map.forEach((key, value) -> System.out.println(key + value));// 1a 2b 3c 4d
computeIfPresent
/**
* 如果指定的 key 存在并且不为 null(不存在则返回 null),则根据旧的 key 和 value 计算新的 value,如果新 value 不为 null,
* 则设置 key 的新 value,并返回新 value。否则,删除指定 key 的 Entry,并返回 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);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
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;
}
}
例:
System.out.println(map.computeIfPresent(4, (key, value) -> key + value));// null (不存在指定的 key,直接返回 null)
System.out.println(map.computeIfPresent(1, (key, value) -> key + value));// 1a (存在指定的 key,将该 key 的值替换为 key + value:1a)
System.out.println(map.computeIfPresent(2, (key, value) -> null));// null (key 存在,但是 value 为 null,于是该 Entry 被删除并返回 null)
map.forEach((key, value) -> System.out.println(key + value));// 11a 3c
compute
/**
* 根据指定的 key 与映射的 value 计算新的 value,
* 新 value 不为 null 时,则设置为 key 的新 value,并返回新 value。
* 否则当旧 value 不为 null 或者 key 存在时,删除 key 对应的 Entry,并返回 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;
* }
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 ConcurrentMap 的所有实现类必须记录该函数是否在原子上应用。
*/
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;
}
}
例:
System.out.println(map.compute(4, (key, value) -> key + value));// 4null (指定 key 虽然不存在,但是新 value 不为 null,于是 key 与 新 value 组成一对映射)
System.out.println(map.compute(1, (key, value) -> key + value));// 1a (指定 key 存在,并且新 value 也不为 null,于是设置 key 的 value 为新 value)
System.out.println(map.compute(2, (key, value) -> null));// null (指定 key 存在,但是新 value 为 null,于是删除 key 对应的 Entry)
map.forEach((key, value) -> System.out.println(key + value));// 11a 3c 44null
marge
/**
* 如果指定的 key 尚未与 value 想关联或与 null 相关联,则将其与给定的非空 value 相关联。
* 否则,将关联的值替换为给定的重映射函数的结果,如果结果为 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);
* }
*
* 默认的实现不保证此方法的同步或原子性。要想保证,其实现必须覆写该方法并记录其并发属性。
* 特别的,只有当该值不存在时,子接口 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);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}
例:
// 指定 key 不存在并且指定 value 不为 null 时,key 与该 value 关联映射,并返回该 value。
System.out.println(map.merge(4, "d", (key, value) -> key + value));// d
// 指定 key 存在但是与指定 value 并不存在映射关系,于是将 key 的 value 设置为新 value,并返回新 value。
System.out.println(map.merge(3, "d", (key, value) -> "new"));// new
// 计算后的新 value 结果为 null,于是移除这个 Entry,并返回 null
System.out.println(map.merge(2, "b", (key, value) -> null));// null
map.forEach((key, value) -> System.out.println (key + value));// 1a 3new 4d
至此,Map 接口中的所有抽象方法、子接口、静态方法以及默认方法全部都理了一遍。
总结
Map 接口作为映射集合的顶层父接口,里面定义了一些必要的操作方法。其中大部分是抽象的,交由具体实现类根据自身的数据结构作具体的实现。另外还包括了 Java 8 中新引入的默认方法、静态方法。