Java 集合中的 AbstractMap 抽象類
jdk1.8.0_144
AbstractMap 抽象類實現了一些簡單且通用的方法, 本身并不難但在這個抽象類中有兩個方法非常值得關注, keySet 和 values 方法源碼的實現可以說是教科書式的典范
抽象類通常作為一種骨架實現, 為各自子類實現公共的方法上一篇我們講解了 Map 接口, 此篇對 AbstractMap 抽象類進行剖析研究
Java 中 Map 類型的數據結構有相當多, AbstractMap 作為它們的骨架實現實現了 Map 接口部分方法, 也就是說為它的子類各種 Map 提供了公共的方法, 沒有實現的方法各種 Map 可能有所不同
抽象類不能通過 new 關鍵字直接創建抽象類的實例, 但它可以有構造方法 AbstractMap 提供了一個 protected 修飾的無參構造方法, 意味著只有它的子類才能訪問 (當然它本身就是一個抽象類, 其他類也不能直接對其實例化), 也就是說只有它的子類才能調用這個無參的構造方法
在 Map 接口中其內部定義了一個 Entry 接口, 這個接口是 Map 映射的內部實現用于維護一個 key-value 鍵值對, key-value 存儲在這個 Map.Entry 中 AbstractMap 對這個內部接口進行了實現, 一共有兩個: 一個是可變的 SimpleEntry 和一個是不可變的 SimpleImmutableEntry
public static class SimpleEntry implements Entry, java.io.Serializable
實現了 Map.Entry 接口, 并且實現了 Serializable(可被序列化)
它的方法比較簡單都是取值存值的操作, 對于 key 值的定義是一個 final 修飾意味著是一個不可變的引用另外其 setValue 方法稍微特殊, 存入 value 值返回的并不是存入的值, 而是返回的以前的舊值需要重點學習的是它重寫的 equals 和 hashCode 方法publicbooleanequals(Objecto){
if(!(oinstanceofMap.Entry))// 判斷參數是否是 Map.Entry 類型, 要 equals 相等首先得是同一個類型
returnfalse;
Map.Entry,?>e=(Map.Entry,?>)o;// 將 Object 類型強轉為 Map.Entry 類型, 這里參數使用? 而不是 K, V 是因為泛型在運行時類型會被擦除, 編譯器不知道具體的 K,V 是什么類型
returneq(key,e.getKey())&&eq(value,e.getValue());//key 和 value 分別調用 eq 方法進行判斷, 都返回 ture 時 equals 才相等
}
privatestaticbooleaneq(Objecto1,Objecto2){
returno1==null?o2==null:o1.equals(o2);// 這個三目運算符也很簡單, 只不過需要注意的是盡管這里 o1o2 是 Object 類型, Object 類型的 equals 方法是通過 == 比較的引用, 所以不要認為這里有問題, 因為在實際中, o1 類型有可能是 String, 盡管被轉為了 Object, 所以此時在調用 equals 方法時還是調用的 String#equals 方法
}
要想正確重寫 equals 方法并能正確使用, 通常還需要重寫 hashCode 方法publicinthashCode(){
return(key==null?0:key.hashCode())^(value==null?0:value.hashCode());//key 和 value 的值不為 null 時, 將它們的 hashCode 進行異或運算
}
publicstaticclassSimpleImmutableEntryimplementsEntry,java.io.SerializableSimpleImmutableEntry
定義為不可變的 Entry, 其實是事實不可變, 因為它不提供 setValue 方法, 在多個線程同時訪問時自然不能通過 setValue 方法進行修改它相比于 SimpleEntry 其 key 和 value 成員變量都被定義為了 final 類型調用 setValue 方法將會拋出 UnsupportedOperationException 異常
它的 equals 和 hashCode 方法和 SimpleEntry 一致
接下來查看 AbstractMap 抽象類實現了哪些 Map 接口中的方法
public int size()
Map 中定義了一個 entrySet 方法, 返回的是 Map.Entry 的 Set 集合, 直接調用 Set 集合的 size 方法即是 Map 的大小
public boolean isEmpty()
調用上面的 size 方法, 等于 0 即為空
public boolean containsKey(Object key)
這個方法的實現較為簡單, 通過調用 entrySet 方法獲取 Set 集合的迭代器遍歷 Map.Entry, 與參數 key 比較 Map 可以存儲為 null 的 key 值, 由于 key=null 在 Map 中存儲比較特殊 (不能計算 hashCode 值), 所以在這里也做了判斷參數 key 是否為空
public boolean containsValue(Object value)
這個方法實現和 containsKey 一致
public V get(Object key)
這個方法實現和上面兩個也類似, 不同的是上面相等返回 boolean, 這個方法返回 value 值
public V put(K key, V value)
向 Map 中存入 key-value 鍵值對的方法并沒有具體實現, 會直接拋出一個 UnsupportedOperationException 異常
public V remove(Object key)
通過參數 key 刪除 Map 中指定的 key-value 鍵值對這個方法也很簡單, 也是通過迭代器遍歷 Map.Entry 的 Set 集合, 找到對應 key 值, 通過調用 Iterator#remove 方法刪除 Map.Entry
public void putAll(Map extends K, ? extends V> m)
這個方法也很簡單遍歷傳入的 Map, 調用 put 方法存入就可以了
public void clear()
調用 entrySet 方法獲取 Set 集合再調用 Set#clear() 方法清空
public Set keySet()
返回 Map key 值的 Set 集合 AbstractMap 中定義了一個成員變量 transient Set keySet, 在 JDK7 中 keySet 變量是由 volatile 修飾的, 但在 JDK8 中并沒有使用 volatile 修飾在對 keySet 變量的注釋中解釋道, 訪問這些字段的方法本身就沒有同步, 加上 volatile 也不能保證線程安全關于 keySet 方法的實現就有點意思了
首先思考該方法是返回 key 值的 Set 集合, 很自然的能想到一個簡單的實現方式, 遍歷 Entry 數組取出 key 值放到 Set 集合中, 類似下面代碼:publicSetkeySet(){
Setks=null;
for(Map.Entryentry:entrySet()){
ks.add(entry.getKey());
}
returnks;
}
這就意味著每次調用 keySet 方法都會遍歷 Entry 數組, 數據量大時效率會大大降低不得不說 JDK 源碼是寫得非常好, 它并沒有采取遍歷的方式如果不遍歷 Entry, 那又如何知道此時 Map 新增了一個 key-value 鍵值對呢?
答案就是在 keySet 方法內部重新實現了一個新的自定義 Set 集合, 在這個自定義 Set 集合中又重寫了 iterator 方法, 這里是關鍵, iterator 方法返回 Iterator 接口, 而在這里又重新實現了 Iterator 迭代器, 通過調用 entrySet 方法再調用它的 iterator 方法下面結合代碼來分析:publicSetkeySet(){
Setks=keySet;// 定義的 transient Set keySet
if(ks==null){// 第一次調用肯定為 null, 則通過下面代碼創建一個 Set 示例
ks=newAbstractSet(){// 創建一個自定義 Set
publicIteratoriterator(){// 重寫 Set 集合的 iterator 方法
returnnewIterator(){// 重新實現 Iterator 接口
privateIterator
V>>i=entrySet().iterator();// 引用 Entry 的 Set 集合 Iterator 迭代器
publicbooleanhasNext(){
returni.hasNext();// 對 key 值的判斷, 就是對 entry 的判斷
}
publicKnext(){
returni.next().getKey();// 取下一個 key 值, 就是取 entry#getKey
}
publicvoidremove(){
i.remove();// 刪除 key 值, 就是刪除 entry
}
};
}
publicintsize(){// 重寫的 Set#size 方法
returnAbstractMap.this.size();//key 值有多少就是整個 Map 有多大, 所以調用本類的 size 方法即可這個是內部類, 直接使用 this 關鍵字代表這個類, 應該指明是調用 AbstractMap 中的 size 方法, 沒有 this 則表示是 static 靜態方法
}
publicbooleanisEmpty(){// 重寫的 Set#isEmpty 方法
returnAbstractMap.this.isEmpty();// 對是否有 key 值, 就是判斷 Map 是否為空,, 所以調用本類的 isEmpty 方法即可
}
publicvoidclear(){// 重寫的 Set#clear 方法
AbstractMap.this.clear();// 清空 key 值, 就是清空 Map,, 所以調用本類的 clear 方法即可
}
publicbooleancontains(Objectk){// 重寫 Set#contains 方法
returnAbstractMap.this.containsKey(k);// 判斷 Set 是否包含數據 k, 就是判斷 Map 中是否包含 key 值, 所以調用本類的 containsKey 方法即可
}
};
keySet=ks;// 將這個自定義 Set 集合賦值給變量 keySet, 在以后再次調用 keySet 方法時, 因為 keySet 不為 null, 只需直接返回
}
returnks;
我認為這是一種很巧妙的實現, 盡管這個方法是圍繞 key 值, 但實際上可以結合 Entry 來實現, 而不用遍歷 Entry, 同時上面提到了調用 entrySet# iterator 方法, 這里則又是模板方法模式的最佳實踐因為 entrySet 在 AbstractMap 中并未實現, 而是交給了它的子類去完成, 但是對于 keySet 方法卻可以對它進行一個算法骨架 實現, 這就是模板方法模式
public Collection values()
對于 values 方法則完全可以參考 keySet, 兩者有著異曲同工之妙, 這里為節省篇幅不再贅述
public abstract Set> entrySet()
一個抽象方法, 交給它的子類去完成, 說明這個方法并不是特別通用
public boolean equals(Object o)
Map 中規定只有在 Map 中的每對 key-value 鍵值對的 key 和 value 都一一對應時他們的 equals 比較才返回 true 在方法中先判斷簡單的條件, 如果引用相等, 直接返回 true, 如果參數 o 不是 Map 類型直接返回 false, 如果兩個 Map 的數量不同也直接返回 false 后面才再遍歷 Entry 數組比較 Entry 中的 key 和 value 是否一一對應方法簡單, 但這給了我們一個啟示, 在條件判斷中, 先判斷簡單的基本的, 再判斷復雜的
public int hashCode()
重寫了 Object 類的 equals 方法, 重寫 hashCode 也是必須的 AbstractMap 對 hashCode 的實現是將所有 Map.Entry(這里就是 SimpleEntry 或 SimpleImmutableEntry) 的 hashCode 值向加, 最后得出的總和作為 Map 的 hashCode 值
public String toString()
這個方法沒什么好說的, 就是取出所有鍵值對使用 StringBuilder 對其進行拼接
protected Object clone() throws CloneNotSupportedException
實現一個淺拷貝, 由于是淺拷貝對于變量 keySet 和 values 不進行拷貝, 防止兩個淺拷貝引發的問題, 關于 Object 中的 clone 方法在萬類之父 Object 已有解析
來源: https://www.cnblogs.com/yulinfeng/p/8486539.html