概述
Map 接口是 java 中兩大集合接口之一,相對於 Collection,Map 接口結構規定了所有鍵值對形式的集合容器。同時,它與 Collection 的子接口 Set 又密切相關,Map 一部分實現依賴於 Set 集合,而 Set 集合的一些實現也依賴於 Map。
Map 接口下有四個主要實現類 TreeMap,HashMap,LinkedMap,Hashtable。基於以上四大實現類,這是他們的類關係圖:
與其相關的還有 Dictionary 類,這是一個已過時的早期鍵值對集合接口,後期的新集合都基於 Map 接口實現,唯一依賴與他的 Hashtable 因爲性能原因也很少被使用,因此這個類是一個過時類。
這是關於 java 集合類源碼的第五篇文章。往期文章:
一、Map 接口
Map 接口就是所有鍵值對類型集合接口的最上層接口,他規定了一個所有 Map 類型集合應該實現的抽象方法,同時提供了一個用於視圖操作的默認接口類 Entry。
1.抽象方法
查詢操作
size()
:獲取鍵值對數量;isEmpty()
:是否爲空;containsKey(Object key)
:是否存在對應的 key;containsValue(Object value)
:是否存在對應的 value;get(Object key)
:根據 key 獲取 value;
更改操作
put(K key, V value)
:添加一對鍵值對;remove(Object key)
:根據 key 刪除相應鍵值對;putAll(Map<? extends K, ? extends V> m)
:合併 Map 集合;clear()
:清空集合;
視圖操作
Map 是一種很特殊的集合,他表示的是一系列鍵值對的映射關係,因而無法像 Collection 集合一樣直接看做一種元素的集合。爲此,Map 定義了實現類必須可以通過以下三種方法返回特定的三個視圖以方便操作:
Set<K> keySet()
:返回一個由 key 組成的 Set 集合視圖;Collection<V> values()
:返回一個由 value 組成的 Collection 集合視圖;entrySet()
:返回一個由 key-value 對組成的 Set 視圖;
其中,entrySet
返回的元素其實就是 Map 的內部接口 Entry 的實現類,一個 Entry 對象表示一對 key 和 value。
2.默認方法
接口的默認方法是 JDK8 新特性之一,因此此類方法全爲 JDK8 新增方法。
這類方法的特點是參數多爲函數式結構,並且大部分不會被實現類重寫。我們可以通過 lambda 表達式去調用。
getOrDefault(Object key, V defaultValue)
:獲取 key 對應的 value,若不存在則返回 defaultValue;forEach(BiConsumer<? super K, ? super V> action)
:遍歷處理集合中的鍵值對;replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
:替換集合中的鍵值對;putIfAbsent(K key, V value)
:僅當對應鍵值對不存在或 value 爲 null 時新增;remove(Object key, Object value)
:僅當對應鍵值存在並且 value 不爲 null 時刪除;replace(K key, V oldValue, V newValue)
:僅當對應鍵值對存在,value 不爲 null 且 value 等於指定 value 時(除非傳入 null)替換舊 value;computeIfAbsent(K key,Function<? super K, ? extends V> mappingFunction)
:如果當前鍵值對不存在,則將通過傳入方法處理 key 得到的 value 與 key 一起添加到集合;computeIfPresent(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)
:如果當前鍵值對存在,則將原先的 value 更新爲通過傳入方法處理後的 value。如果處理後得到 null,則刪除對應的鍵值對。compute(K key,BiFunction<? super K, ? super V, ? extends V> remappingFunction)
:如果當前鍵值對存在,並且根據傳入方法處理後得到的新 value 不爲null,則更新鍵值對,否則則刪除鍵值對。merge(K key, V value,BiFunction<? super V, ? super V, ? extends V> remappingFunction)
:如果當前鍵值對不存在,則新 value 等於當前值,否則由根據傳入方法處理得到。若新 value 不爲 null,則更新鍵值對,否則就刪除鍵值對。
3.equals和hashcode
在 Map 中,由於多數比較基於 equals()
和 hashCode()
方法,因此 Map 集合要求實現類重寫實現這兩個方法,其中:
equals()
要求以 m1.entrySet().equals(m2.entrySet())
的形式實現兩個 Map 集和的比較;
而 hashCode()
則與 Collection 的 equals()
類似,要求 Map 集合的 hashcode 應當爲集合內所有 entrySet()
的 hashcode 之和。
二、Entry 接口
Entry 是 Map 的一個內部接口類。跟一般的接口不太一樣,Entry 的實現類實際上是作爲 Map 中鍵值對對象使用的,即一對 key 和 value 就作爲一個 Entry 對象。Entry 實際上是對 Map 實現類中使用的鍵值對對象的一種約束。根據 JavaDoc 的說明:
Map.entrySet方法返回 Map 的集合視圖,該 Map 的元素屬於此類。
這些 Map.Entry 對象僅在迭代期間有效。
他提供了九個基本的方法:
getKey()
:獲取 key;getValue
:獲取 value;comparingByKey()
:根據 key 排序;comparingByValue()
:根據 value 排序;comparingByKey(Comparator<? super K> cmp)
:根據 key 排序;comparingByValue(Comparator<? super V> cmp)
:根據 value 排序。
我們也可以把 Entry 看成 Map 集合中的一種視圖,只不過它只侷限於一對鍵值對。
三、AbstractMap 抽象類
AbstractMap 與 AbstractList 一樣,都是爲於接口提供基本實現的抽象類。根據 JavaDoc,我們可以簡單的瞭解一下它:
此類提供Map接口的基本實現,以最大程度地減少實現此接口所需的工作。
要實現不可修改的Map,程序員僅需要擴展此類併爲 entrySet 方法提供實現,該方法將返回 Map 映射的 set-view。
通常,返回的集合將依次在 AbstractSet 之上實現。 此集合不支持add或remove方法,並且其迭代器不支持remove方法。
要實現可修改的Map,程序員必須另外重寫此類的put方法(否則將引發UnsupportedOperationException ),並且entrySet()。iterator()返回的迭代器必須另外實現其 remove 方法。
1.成員變量
AbstractMap 內部擁有兩個成員變量 keySet
和 values
:
// key的集合視圖
transient Set<K> keySet;
// values的集合視圖
transient Collection<V> values;
值得一提的是,針對獲取 keySet 的同名抽象方法 keySet()
的註釋上,指明瞭:
這些字段中的每個字段都被初始化爲在第一次請求此視圖時包含相應視圖的實例。 視圖是無狀態的,因此沒有理由創建多個視圖。
同樣,實現也必須只讀取一次該字段,如:
public Set<K> keySet() { Set<K> ks = keySet; // single racy read if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; }
可見,keySet 獲取實例是一個很典型的單例模式。
2.內部類
之前我們說,Map 接口提供的內部類接口 Entry,是爲實現類的鍵值對對象提供約束。在 AbstractMap 中,就提供了兩個實現了 Entry 接口的內部類 SimpleEntry 和 SimpleImmutableEntry。
SimpleEntry
SimpleEntry 是一個基於 Map 接口的內部接口類 Entry 的實現。他可以看成是集合中一對鍵值對對象的映射——或者說,視圖。
當初始化時,需要傳入 key 和 value 賦值給同名的成員變量,並且 key 會被 final 修飾,因而 key 不可更改,而 value 可以更改。
基於以上的原理,當 value 是引用類型,並且去修改它的時候,修改會真實的反應到外部類的 values 對應的那個 value 中,但是如果要做替換,則只能替換當前 SimpleEntry 對象中的 value。
public static class SimpleEntry<K,V> implements Entry<K,V>, java.io.Serializable{
private static final long serialVersionUID = -8499721149061103585L;
// key不可變
private final K key;
private V value;
public SimpleEntry(K key, V value) {
this.key = key;
this.value = value;
}
public SimpleEntry(Entry<? extends K, ? extends V> entry) {
this.key = entry.getKey();
this.value = entry.getValue();
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
// 更新值
public V setValue(V value) {
V oldValue = this.value;
this.value = value;
return oldValue;
}
// 重寫equals方法,要求key與value都需要相等
public boolean equals(Object o) {
if (!(o instanceof Map.Entry))
return false;
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
return eq(key, e.getKey()) && eq(value, e.getValue());
}
public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^
(value == null ? 0 : value.hashCode());
}
public String toString() {
return key + "=" + value;
}
}
SimpleImmutableEntry
SimpleImmutableEntry 與 SimpleEntry 沒什麼不同,只不過多了和他名字一樣的特性:“不可變”。
相對於 SimpleEntry,SimpleImmutableEntry 的 value 也被 final 修飾,並且調用它的 setValue()
方法會拋出 UnsupportedOperationException
異常。
public static class SimpleImmutableEntry<K,V> implements Entry<K,V>, java.io.Serializable {
private final K key;
private final V value;
public V setValue(V value) {
throw new UnsupportedOperationException();
}
}
五、AbstractMap 的視圖
1.四個視圖
我們知道,由於 Map 是一個鍵值對集合,因此它實際存放的是 key 與 value,還有他們的映射關係。單個操作的時候很好操作,要麼根據 key 找 value,要麼直接 找 key 或者 value。但是當迭代的時候,我們就需要區分,是要迭代全部的 key,還是迭代全部的 value,還是要同時迭代全部的 key + value。因此,就有了 Map 的三個視圖:
- key 視圖 keySet:用於存放 key 的 Set 集合;
- value 視圖 values:用於存放 value 的 Collection 集合;
- key-value 視圖 entrySet:用於存放 key-value 的視圖 Entry 的 Set 集合。
entrySet 比較不好理解,它裏面放的是 Entry,一個 Entry 就代表一對鍵值對,因此,Entry 其實也可以理解爲一個視圖,這個視圖只侷限於一對鍵值對。
而 entrySet 裝了集合裏面全部的 Entry 視圖,所以它也可以理解爲一個表示整個 Map 容器中所有 key-value 的視圖。或者簡單粗暴的理解爲entrySet = keySet + values
。
由於 Entry 代表了鍵值對集合,因此也可以把 Map 集合以“Entry 對象的 Set 集合”——也就是 entrySet——表示。因此,在 Map 的實現類中,Entry 是一個很特別的類,他在很多的方法裏都被作爲參數使用。
2.視圖類的實現
雖然定義好了視圖類,但是 Map 接口並沒有提供關於 keySet,values,Entry 與 entrySet 的實現。 但是定義了用於獲取三個視圖的方法 keySet()
,values()
和 enrtySet()
。
AbstractMap 在上述基礎上,提供了 keySet 和 values 作爲成員變量,entrySet 變量需要由實現類自己去提供。
因此,如果一個實現類要實現 Map 接口,AbstractMap,理論上需要提供7個關於視圖的實現類:
- KeySet 視圖實現類,以及它的迭代器類;
- Values 視圖實現類,以及它的迭代器;
- Entry 視圖實現類;
- EntrySet 類,以及它的迭代器;
而在 AbstractMap 中,不提供 Entry 和 EntrySet 的實現,並且讓 entrySet()
方法仍然保持抽象狀態。
但是以匿名內部類的形式實現了 KeySet 和 Values 視圖,並且讓兩者的迭代器都使用 entrySet()
方法返回的 EntrySet 實現類提供的迭代器。
也就是說,如果一個類要實現 Map 接口,那麼通過繼承 AbstractMap,它只需要再提供3個實現類就可以了:
- EntrySet 實現類,以及它的迭代器;
- Entry 視圖實現類。
3.keySet()
keySet()
方法用於獲取集合內部存放 key 的 Set 集合 keySet。他直接返回了一個繼承並且實現了 AbstractSet 抽象方法 iterator()
的匿名內部類,並且直接使用 EntrySet 的迭代器。
public Set<K> keySet() {
// 獲取 keySet
Set<K> ks = keySet;
// 如果keySet爲null,就創建一個自定義的 AbstractSet
if (ks == null) {
ks = new AbstractSet<K>() {
// 實現了AbstractSet中的iterator()方法
public Iterator<K> iterator() {
// 返回一個自定義的迭代器類
return new Iterator<K>() {
// 獲取entrySet返回的Set集合的迭代器,以下方法全部基於改迭代器實現
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public K next() {
return i.next().getKey();
}
public void remove() {
i.remove();
}
};
}
// 以下方法全部都調用AbstractMap的實現
public int size() {
return AbstractMap.this.size();
}
public boolean isEmpty() {
return AbstractMap.this.isEmpty();
}
public void clear() {
AbstractMap.this.clear();
}
public boolean contains(Object k) {
return AbstractMap.this.containsKey(k);
}
};
keySet = ks;
}
return ks;
}
也就是說,AbstractMap 中使用的 keySet 相當於一個單例的 AbstractSet 內部實現類,這類的迭代器就是 entrySet()
方法返回的 Set 集合的迭代器;而其他的方法直接使用外部類 AbstractMap 的:
4.values()
AbstractMap 的 values()
方法和 keySet()
類似。它返回一個繼承了 AbstractCollection 抽象類的匿名內部類:
public Collection<V> values() {
Collection<V> vals = values;
if (vals == null) {
vals = new AbstractCollection<V>() {
// 使用entrySet().iterator()獲取的迭代器
public Iterator<V> iterator() {
return new Iterator<V>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();
public boolean hasNext() {
return i.hasNext();
}
public V next() {
return i.next().getValue();
}
public void remove() {
i.remove();
}
};
}
// 直接使用AbstractMap提供的方法
public int size() {
return AbstractMap.this.size();
}
public boolean isEmpty() {
return AbstractMap.this.isEmpty();
}
public void clear() {
AbstractMap.this.clear();
}
public boolean contains(Object v) {
return AbstractMap.this.containsValue(v);
}
};
values = vals;
}
return vals;
}
五、AbstractMap 的方法
1.不支持的實現
如類註釋所說,AbstractMap 不支持 put()
public V put(K key, V value) {
throw new UnsupportedOperationException();
}
因此經過已經實現了部分邏輯,但是 putAll()
在實現 put()
方法之前也無法使用:
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
put(e.getKey(), e.getValue());
}
2.實現的方法
查詢操作
public int size() {
return entrySet().size();
}
public boolean isEmpty() {
return size() == 0;
}
更改操作
// get
public V get(Object key) {
Iterator<Entry<K,V>> i = entrySet().iterator();
// 指定要獲取的key是否爲null
if (key==null) {
while (i.hasNext()) {
Entry<K,V> e = i.next();
// 返回對應的value
if (e.getKey()==null)
return e.getValue();
}
} else {
while (i.hasNext()) {
Entry<K,V> e = i.next();
if (key.equals(e.getKey()))
return e.getValue();
}
}
return null;
}
// remove
public V remove(Object key) {
// 獲取entrySet的迭代器
Iterator<Entry<K,V>> i = entrySet().iterator();
Entry<K,V> correctEntry = null;
// 指定要刪除的key是否爲null
if (key==null) {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
// 找出key爲null的那對鍵值對
if (e.getKey()==null)
correctEntry = e;
}
} else {
while (correctEntry==null && i.hasNext()) {
Entry<K,V> e = i.next();
// 找出指定鍵值對
if (key.equals(e.getKey()))
correctEntry = e;
}
}
V oldValue = null;
if (correctEntry !=null) {
// 根據key獲取value
oldValue = correctEntry.getValue();
// 刪除該鍵值對
i.remove();
}
return oldValue;
}
// clear
public void clear() {
entrySet().clear();
}
3.equals / hashCode
集合容器基本都會重寫 equals()
和 hashCode()
方法,AbstractMap 亦然。
equals
public boolean equals(Object o) {
// 是否同一個對象
if (o == this)
return true;
// 是否實現Map接口
if (!(o instanceof Map))
return false;
Map<?,?> m = (Map<?,?>) o;
// 是否長度相同
if (m.size() != size())
return false;
try {
// 遍歷自己的鍵值對
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext()) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
// value是否爲null
if (value == null) {
// 是否在比較的集合中key存在,並且對應的value是null
if (!(m.get(key)==null && m.containsKey(key)))
return false;
} else {
// 若value不爲null,比較是否value一致
if (!value.equals(m.get(key)))
return false;
}
}
} catch (ClassCastException unused) {
return false;
} catch (NullPointerException unused) {
return false;
}
return true;
}
hashCode
AbstractMap 的 hashcode 是集合內所有 entrySet 對象的 hashcode 之和。
public int hashCode() {
int h = 0;
Iterator<Entry<K,V>> i = entrySet().iterator();
while (i.hasNext())
h += i.next().hashCode();
return h;
}
4.toSring
public String toString() {
Iterator<Entry<K,V>> i = entrySet().iterator();
if (! i.hasNext())
return "{}";
StringBuilder sb = new StringBuilder();
sb.append('{');
for (;;) {
Entry<K,V> e = i.next();
K key = e.getKey();
V value = e.getValue();
sb.append(key == this ? "(this Map)" : key);
sb.append('=');
sb.append(value == this ? "(this Map)" : value);
if (! i.hasNext())
return sb.append('}').toString();
sb.append(',').append(' ');
}
}
5.clone
protected Object clone() throws CloneNotSupportedException {
AbstractMap<?,?> result = (AbstractMap<?,?>)super.clone();
result.keySet = null;
result.values = null;
return result;
}
六、總結
Map接口
Map 接口規定了所有鍵值對類型的集合容器。他底下有 Hashtable,HashMap,TreeMap,以及 HashMap 的子類 LinkedMap 四個主要的實現類。
與他類似的還有 Dictionary 接口,Hashtable 實現了該接口。這是一個已經過時的鍵值對容器接口。
AbstractMap 抽象類爲 Map 接口提供了大部分的實現。處理 Hashtable 外,其他三個主要實現類都繼承了他。
在 Map 接口中,提供了鍵值對視圖的接口 Entry,並且規定實現類需要實現 entrySet(),keySet(),values() 三個抽象方法,以返回 Entry 的 Set 集合視圖,key 的 Set 集合視圖以及 value 的 Collection 集合視圖。
AbstractMap
在 AbstractMap 中,實現了 keySet()
,values()
方法,並且提供了相應的成員變量 keySet 和 values 。但是沒有提供 EntrySet 的實現類,也沒有實現 entrySet()
方法。
用於獲取 key 的keySet()
方法會返回一個 AbstractSet 的匿名實現類,迭代器通過 entrySet()
獲取實現類實現的 EntrySet 類的迭代器, 而其他方法直接使用 AbstractMap 的。用於獲取 value 的values()
方法也是如此,只不過返回的是一個 AbstractCollection 的匿名實現類。
針對 Entry 接口,AbstractMap 提供了 SimpleEntry 與 SimpleImmutableEntry 兩個類,相對前者,後者的 value 被 final 修飾,並且調用 setValue()
方法會拋出 UnsupportedOperationException 異常,因而是不可變的。