Java基礎系列(四十五):集合之Map
簡介
Map
是一個介面,代表的是將鍵對映到值的物件。一個對映不能包含重複的鍵,每個鍵最多隻能對映到一個值。
Map
介面提供了三種collection
檢視,允許以鍵集、值集或鍵-值對映關係集的形式檢視某個對映的內容。對映順序 定義為迭代器在對映的 collection
檢視上返回其元素的順序。某些對映實現可明確保證其順序,如 TreeMap
類;另一些對映實現則不保證順序,如 HashMap
類。
下面,我們去通過原始碼中看一看Map
都給我們提供了哪些功能,以及一些方法的用法。
增 or 改:
/**
* 將指定的值與此對映中的指定鍵關聯。
* 如果此對映以前包含一個該鍵的對映關係,則用指定值替換舊值
* @param key 與指定值關聯的鍵
* @param value 與指定鍵關聯的值
* @return 以前與 key 關聯的值,如果沒有針對 key 的對映關係,則返回 null。
*/
V put(K key, V value);
/**
* 從指定對映中將所有對映關係複製到此對映中
* @param m 要儲存在此對映中的對映關係
*/
void putAll(Map<? extends K, ? extends V> m);
可以看出,在 Java 8 之前提供了這兩個向對映中新增對映關係的方法,這兩個方法我們已經耳熟能詳,下面我們來看一下檢視元素的方法。
查:
/**
* 返回指定鍵所對映的值;如果此對映不包含該鍵的對映關係,則返回 null。
* @param key 要返回其關聯值的鍵
* @return 指定鍵所對映的值;如果此對映不包含該鍵的對映關係,則返回 null
*/
V get(Object key);
這裡的前提是你必須知道對映中的鍵,才能獲取對映中的值。但是我們在前面說過,Map
介面提供了三個collection
的檢視,我們可以使用這些檢視來去獲取Map
中的元素
/**
* 返回此對映中包含的鍵的 Set 檢視。
* @return 此對映中包含的鍵的 set 檢視
*/
Set<K> keySet();
/**
* 返回此對映中包含的值的 Collection 檢視。
* @return 此對映中包含的值的 collection 檢視
*/
Collection<V> values();
/**
* 返回此對映中包含的對映關係的 Set 檢視。
* @return 此對映中包含的對映關係的 set 檢視
*/
Set<Entry<K, V>> entrySet();
當然,還有在 Java 8 新增的forEach
方法也可以遍歷獲取Map
中的值
/**
* 遍歷集合,這裡的引數是一個函式式介面,可以結合Lambda表示式去優雅的使用
* @param action 進行的操作,函式式介面
*/
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
//其實本質上還是用entrySet()獲取鍵值對後進行遍歷的
for (Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
我們可以這樣去使用forEach
方法:
Map<String, String> map = new HashMap<>();
map.forEach((k, v) -> System.out.println("key:value = " + k + ":" + v));
當然,我們可以這樣的去優雅去遍歷一個集合:
//獲取key檢視
map.keySet().forEach(s -> System.out.println("Key:Value=" + s + ":" + map.get(s)));
//獲取鍵值對檢視
map.entrySet().forEach(entry -> System.out.println("Key:Value=" + entry.getKey() + ":" + entry.getValue);
//獲取值檢視
map.values.forEach(s -> System.out.println("Value" + s));
下面我們來看一下Map
的刪:
刪:
/**
* 如果存在一個鍵的對映關係,則將其從此對映中移除
* @param key 從對映中移除其對映關係的鍵
* @return 以前與 key 關聯的值;如果沒有 key 的對映關係,則返回 null。
*/
V remove(Object key);
/**
* 從此對映中移除所有對映關係,該方法被呼叫後,該對映將為空。
*/
void clear();
hashCode()
和equals()
也在Map
中被重新定義了:
/**
* 比較指定的物件與此對映是否相等。如果給定的物件也是一個對映,並且這兩個對映表示相同的對映關係,則返回 true。更確切地講,
* 如果 m1.entrySet().equals(m2.entrySet()),則兩個對映 m1 和 m2 表示相同的對映關係。這可以確保 equals 方法在不同的 Map 介面實現間執行正常。
* @param o
* @return
*/
@Override
boolean equals(Object o);
/**
* 返回此對映的雜湊碼值。對映的雜湊碼定義為此對映 entrySet() 檢視中每個項的雜湊碼之和。
* 這確保 m1.equals(m2) 對於任意兩個對映 m1 和 m2 而言,都意味著 m1.hashCode()==m2.hashCode(),正如 Object.hashCode() 常規協定的要求。
* @return
*/
@Override
int hashCode();
在這裡只是定義了一個介面,而在具體的實現時,必須遵循這裡所規定的一些規則,對映的雜湊碼定義為此對映 entrySet() 檢視中每個項的雜湊碼之和。這樣才能保證equals
方法在不同的 Map
介面實現間執行正常。
在 Java 8 之後,新增了一些default方法可以配合lambda表示式去使用,我們一起來看一下這幾個方法:
JDK1.8新特性
/**
* 根據對映的鍵進行排序
*/
public static <K extends Comparable<? super K>, V> Comparator<Entry<K,V>> comparingByKey() {
return (Comparator<Entry<K, V>> & Serializable)
(c1, c2) -> c1.getKey().compareTo(c2.getKey());
}
/**
* 通過指定的比較器根據對映的鍵進行排序
*/
public static <K, V> Comparator<Entry<K, V>> comparingByKey(Comparator<? super K> cmp) {
Objects.requireNonNull(cmp);
return (Comparator<Entry<K, V>> & Serializable)
(c1, c2) -> cmp.compare(c1.getKey(), c2.getKey());
}
首先來說的是Map
的子介面Entry
中的comparingByKey()
方法,這個方法所起到的作用是按照對映的鍵進行排序,我們接下來來看一下怎麼取用:
public class Test {
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.put("E","test5");
map.put("D","test4");
map.put("C","test3");
Stream<Map.Entry<String, String>> sorted = map.entrySet().stream().sorted(Map.Entry.comparingByKey());
Stream<Map.Entry<String, String>> sorted2 = map.entrySet().stream().sorted(Map.Entry.comparingByKey(String::compareTo));
sorted.forEach(entry -> System.out.println(entry.getValue()));
System.out.println("===============");
sorted2.forEach(entry -> System.out.println(entry.getValue()));
}
}
輸出結果為:
test1
test2
test3
test4
test5
===============
test1
test2
test3
test4
test5
可以看到,這是被排序後的結果,至於comparingByValue
方法,其實用法和comparingByKey()
基本上一樣,所以這裡不再多做解說,forEach的用法,我們在前面已經進行了瞭解,下面,我們來看一下getOrDefault()
方法
/**
* 如果對映中存在於key相對應的value,則返回這個value,否則返回defaultValue
* @param key 指定值的鍵,如果該value不存在,返回defaultValue
* @param defaultValue 如果指定鍵的值不存在,返回這個值
* @return 如果對映中存在於key相對應的value,則返回這個value,否則返回defaultValue
*/
default V getOrDefault(Object key, V defaultValue) {
V v;
return (((v = get(key)) != null) || containsKey(key))
? v
: defaultValue;
}
下面,我寫了一個小demo來讓大家看看這個方法怎麼用
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
String value = map.getOrDefault("A", "test2");
System.out.println(value);
String defaultValue = map.getOrDefault("C", "test1");
System.out.println(defaultValue);
}
輸出的結果為:
test1
test1
可以看出,第一個獲取到的是鍵“A”對應的值“test1”,第二次呼叫獲取的是defaultValue—“test1”。下面,我們來看一下replace
家族的一些成員:
/**
* 對對映中的所有鍵值對執行計算,並將返回結果作為value覆蓋
* map.replaceAll((k,v)->((String)k).length());
* @param function 執行的操作,函式式介面
*/
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
...
}
/**
* 當且僅當 key 存在,並且對應值與 oldValue 不相等,才用 newValue 作為 key 的新相關聯值,返回值為是否進行了替換。
* @param key 與指定值相關聯的鍵
* @param oldValue 預期與指定鍵相關聯的值
* @param newValue 與指定鍵相關聯的值
* @return 如果該值被替換,返回true
*/
default boolean replace(K key, V oldValue, V newValue) {
...
}
/**
* 只有當目標對映到某個值時,才能替換指定鍵的條目。
* @param key 與指定值相關聯的鍵
* @param value 與指定鍵相關聯的值
* @return 與指定鍵相關聯的上一個值,如果沒有鍵的對映,返回null
*/
default V replace(K key, V value) {
...
}
接下來,我寫了一個小demo來看看這三個方法的用法:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.replaceAll((s, s2) -> {
return s + s2;
});
printMap(map);
map.replace("A","test1");
printMap(map);
map.replace("A","test2","test1");
printMap(map);
map.replace("A","test1","test2");
printMap(map);
}
public static void printMap(Map<String,String> map){
map.forEach((key, value) -> System.out.print(key + ":" + value + " "));
System.out.println();
}
列印結果:
A:Atest1 B:Btest2
A:test1 B:Btest2
A:test1 B:Btest2
A:test2 B:Btest2
一切正如我們所想象的那般發展,接下來,我們來看一下compute
三兄弟。
/**
* 如果指定的鍵尚未與值相關聯(或對映到null),則嘗試使用給定的對映函式計算其值,並將其輸入到此對映中,除非null 。
* @param key 指定值與之關聯的鍵
* @param mappingFunction 計算值的函式
* @return 與指定鍵相關聯的當前(現有或計算)值,如果計算值為空,則為null
*/
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
...
}
/**
* 如果指定的key的值存在且非空,則嘗試計算給定鍵及其當前對映值的新對映。
* @param key 指定值與之關聯的鍵
* @param remappingFunction 計算值的函式
* @return 與指定鍵相關的新值,如果沒有則為null
*/
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
}
/**
* 嘗試計算指定key及其當前對映值的對映(如果沒有當前對映,則null )。
* @param key 指定值與之關聯的鍵
* @param remappingFunction 計算值的函式
* @return 與指定鍵相關的新值,如果沒有則為null
*/
default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
...
}
現在,我們來看一下,這三個方法是怎麼用的,以及他們的不同。
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.compute("A", (key, value) -> { return key + value;});
printMap(map);
//因為,集合中存在“A”,所以這裡沒有進行相應的操作
map.computeIfAbsent("A", (key) -> { return key + 2;});
printMap(map);
//這裡因為集合中不存在“C”,所以進行了賦值的操作
map.computeIfAbsent("C", (key) -> { return key + 2;});
printMap(map);
//這裡由於集合存在“A”,根據方法定義,會計算後返回給原值
map.computeIfPresent("A", (key, value) -> { return key + value;});
printMap(map);
//這裡由於不存在“D”,根據方法定義,不做任何操作
map.computeIfPresent("D", (key, value) -> { return key + value;});
printMap(map);
}
public static void printMap(Map<String,String> map){
map.forEach((key, value) -> System.out.print(key + ":" + value + " "));
System.out.println();
}
輸出結果:
A:Atest1 B:test2
A:Atest1 B:test2
A:Atest1 B:test2 C:C2
A:AAtest1 B:test2 C:C2
A:AAtest1 B:test2 C:C2
那麼,最後剩下的還有一個類似於compute
的merge
方法以及remove
方法和putIfAbsent
方法,接下來我們來看看這幾個方法。
/**
* 如果key在集合中的value為空或則鍵值對不存在,則用引數value覆蓋
* @param key 如果key存在且不為null,返回key對應的value,如果不存在,呼叫put(key,value)
* @param value 如果key對應的值不存在或者為null,將該value與key進行對應
* @return 返回的是被替代的值
*/
default V putIfAbsent(K key, V value) {
...
}
/**
* key 與 value 都匹配時才刪除。
* @param key 被刪除的對映關係的key
* @param value 被刪除的對映關係的value
* @return 返回的是否刪除成功
*/
default boolean remove(Object key, Object value) {
...
}
/**
* 如果指定的鍵尚未與值相關聯或與null相關聯,則將其與給定的非空值相關聯。
* @param key 結合值與之關聯的鍵
* @param value 要與與key相關聯的現有值合併的非空值,或者如果沒有現有值或空值與key相關聯,則與該key相關聯
* @param remappingFunction 重新計算值(如果存在)的功能
* @return 與指定鍵相關聯的新值,如果沒有值與該鍵相關聯,則返回null
*/
default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
}
接下來,我們接著來看一個例子:
public static void main(String[] args) {
Map<String, String> map = new HashMap<String,String>();
map.put("A","test1");
map.put("B","test2");
map.putIfAbsent("A","test2");
map.putIfAbsent("C","test3");
printMap(map);
map.remove("A","test1");
printMap(map);
map.merge("A","test1",(oldValue, newValue) ->{
return oldValue + newValue;
} );
printMap(map);
map.merge("A","test4",(oldValue, newValue) ->{
return newValue;
} );
printMap(map);
}
輸出的是:
A:test1 B:test2 C:test3
B:test2 C:test3
A:test1 B:test2 C:test3
A:test4 B:test2 C:test3
到這裡,Map
的講解就要和大家告一段落了,下篇AbstractMap
再見~
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。