一 JDK中的Map继承实现关系
不经意间看了Java中LinkedHashMap和LinkedHashSet的源码实现,觉得一些地方还是挺有意思的。之前阅读过一些,但没有进行系统性地总结,打算尝试一下Map源码的系统性整理学习。因为Java中的Set底层基本上是借助对应的Map实现的,故Set打算放在Map之后学习。所使用的jdk版本为1.8版本,先看一下JDK中Map的UML类图:
这张图囊括了JDK中绝大部分的Map(没有将java.util.Collections中的一些private static map结构、java.util.EnumMap等列入),后面逐一分析,希望能有所收获。
二 源码分析学习
2.1 相关接口
2.1.1 Map接口
Map<K,V>是最基本的接口,它表示将键映射到值的对象。一个映射不能包含重复的键;每个键最多只能映射到一个值。此接口被设计用来取代 java.util.Dictionary 类,后者完全是一个抽象类,而不是一个接口。因为Map涉及到Key和Value两个对象,所以它不像List<E>或者Set<E>接口都实现了Collection<E>接口,Map<K,V>没有继承任何接口。有些Map的实现可以保证顺序,如TreeMap;有些Map的实现不保证顺序,如HashMap;还有特殊的Map实现比如LinkedHashMap具有可预知的迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。
下面列一些需要注意的地方:
2.1.1.1 containsKey(Object key)方法
Map中需要注意的containsKey(Object key)方法,Map是否已经包含有某个对象key的判断条件:当且仅当此映射包含针对满足 (key==null ? k==null : key.equals(k)) 的键 k 的映射关系时,返回 true。(最多只能有一个这样的映射关系)。可能日常开发中常用字符串String作为key,而String已经帮我们实现了Object类中的equals()方法和hashCode()方法,所以当我们使用自定义的对象作为key时,一定要考虑这两个方法的实现。containsKey(Object key)定义如下:
/**
* Returns <tt>true</tt> if this map contains a mapping for the specified
* key. More formally, returns <tt>true</tt> if and only if
* this map contains a mapping for a key <tt>k</tt> such that
* <tt>(key==null ? k==null : key.equals(k))</tt>. (There can be
* at most one such mapping.)
*
* @param key key whose presence in this map is to be tested
* @return <tt>true</tt> if this map contains a mapping for the specified
* key
* @throws ClassCastException if the key is of an inappropriate type for
* this map
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
* @throws NullPointerException if the specified key is null and this map
* does not permit null keys
* (<a href="{@docRoot}/java/util/Collection.html#optional-restrictions">optional</a>)
*/
boolean containsKey(Object key);
2.1.1.2 entrySet()方法和内部接口Entry<K,V>
Map接口中还定义了一个内部接口Entry<K,V>,用来表示Map中的映射项;另外有一个方法Set<Map.Entry<K, V>> entrySet();两者结合可以用来遍历Map中的元素。比如下面写法:
for (Map.Entry<String, Object> entry : map.entrySet()) {
System.out.println("key:value = " + entry.getKey() + ":" + entry.getValue());
}
2.1.1.3 V put(K key, V value)方法
将映射关系放入Map结构中,需要注意的是如果此映射以前包含一个该键的映射关系,则用指定值替换旧值(当且仅当m.containsKey(k)返回 true 时,才能说映射 m 包含键 k 的映射关系)。
返回值:返回之前与 key 关联的旧值,如果没有针对 key 的映射关系,则返回 null。(如果该实现支持 null 值,则返回 null 也可能表示此映射以前将 null 与 key 关联)。
2.1.1.4 Set<K> keySet() 和 Collection<V> values()
Set<K> keySet()方法返回Map中所有映射关系中key的集合,因为key是不能重复的,所以返回的是Set结构;Collection<V> value()方法返回Map中所有映射关系值value的集合,value的值是可以重复的,所以返回的是Collection结构,重复的value值也会重复返回,不会去重。
通过keySet()方法和get(Object key)方法也能遍历Map:
for (String key : map.keySet()) {
System.out.println("map.get(" + key + ") = " + map.get(key));
}
2.1.1.5 equals(Object o) 和 hashCode()
equals(Object o)方法用来比较两个Map是否相等,如果给定的对象也是一个映射,并且这两个映射表示相同的映射关系,则返回 true。更确切地讲,如果 m1.entrySet().equals(m2.entrySet()),则两个映射 m1 和 m2 表示相同的映射关系。
hashCode()方法返回此映射的哈希码值。映射的哈希码定义为此映射 entrySet() 中每个项的哈希码之和。这确保 m1.equals(m2) 对于任意两个映射 m1 和 m2 而言,都意味着 m1.hashCode()==m2.hashCode(),正如Object.hashCode()中的要求。
equals(Object o)和hashCode()的具体实现,可以参考java.util.AbstractMap中的实现,后面也会介绍。
2.1.1.6 JDK1.8版本新增加的特性
1、default V getOrDefault(Object key, V defaultValue)
一个default方法,返回Map中key对应的value值,如果没有这个key,返回传入的默认值defaultValue:
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
使用示例:
public static void main(String[] args) {
Map map = new HashMap();
map.put("1", 12);
map.put("11", 12);
map.put("12", 13);
System.out.println(map.getOrDefault("11",120));
System.out.println(map.getOrDefault("13",120));
}
输出结果:
12
120
2、default void forEach(BiConsumer<? super K, ? super V> action)
该forEach方法采用函数式编程,传入一个函数式接口BiConsumer,用来迭代遍历Map,如下:
map.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v));
3、default void repalceAll(BiFunction<? super K, ? super V> action)
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);
}
}
}
用给定的函数action对Map中的每个映射进行处理,并用每个映射执行action返回的结果替换映射对应的value值。举个例子:
public static void main(String[] args) {
Map<String,Integer> map = new HashMap();
map.put("1", 1);
map.put("2", 2);
map.put("3", 3);
System.out.println(map);
//对map中的每个映射value值都加1
map.replaceAll((k,v) -> v +=1);
System.out.println(map);
}
输出结果:
{1=1, 2=2, 3=3}
{1=2, 2=3, 3=4}
4、default V putIfAbsent(K key, V value)
default V putIfAbsent(K key, V value) {
V v = get(key);
if (v == null) {
v = put(key, value);
}
return v;
}
如果入参中的key在Map中没有映射的value值或者映射的value值为null,把入参中的value值与之关联,返回null。反之,返回Map中该key映射的value值。
使用示例:
public static void main(String[] args) {
Map<String, Integer> map = new HashMap();
map.put("1", 1);
map.put("2", null);
System.out.println("初始map:" + map);
System.out.println("map.putIfAbsent(\"1\", 11)返回值:" + map.putIfAbsent("1", 11));
System.out.println("map.putIfAbsent(\"2\", 2)返回值:" + map.putIfAbsent("2", 2));
System.out.println("map.putIfAbsent(\"3\", 3)返回值:" + map.putIfAbsent("3", 3));
System.out.println("处理后map:" + map);
}
输出结果:
初始map:{1=1, 2=null}
map.putIfAbsent("1", 11)返回值:1
map.putIfAbsent("2", 2)返回值:null
map.putIfAbsent("3", 3)返回值:null
处理后map:{1=1, 2=2, 3=3}
注:还有一些其它的default函数,这里不再一一列举了,都是JDK1.8中根据default特性新加在Map接口中的,大家可以自己了解下,实际开发使用会便利一些。
以上,Map接口暂时就写这么多~