1. 程式人生 > >Java基礎系列(四十五):集合之Map

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  

那麼,最後剩下的還有一個類似於computemerge方法以及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再見~

原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。


公眾號