一 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接口暫時就寫這麼多~