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