自頂向下理解Java集合框架(三)Map接口

Map基本概念html

數據結構中Map是一種重要的形式。Map接口定義的是查詢表,或稱查找表,其用於儲存所謂的鍵/值對(key-value pair),其中key是映射表的索引。java

JDK結構中還存在實現Map相似功能的遺留集合:安全

Hashtable(線程安全的散列映射表)數據結構

Properties(屬性映射表),經常使用於配置文件(如db.properties)。框架

Map接口源代碼ide

package java.util;

/* 映射表(查詢表)泛型接口 */
public interface Map<K,V> { /* 基本與Collection相同,返回映射表中key-value映射對數量(內部屬性size) */ int size(); /* 基本與Collection相同,返回映射表是否包含映射對 */ boolean isEmpty(); /* 返回映射表是否包含指定的鍵 */ boolean containsKey(Object key); /* 返回映射表是否包含指定的值,或者說指定的值是否有大於等於1個映射的鍵 */ boolean containsValue(Object value); /* 若是映射表中存在指定key,返回此key的值;不然,返回null */ V get(Object key); /* 將鍵值對放入映射表中。
* 若是鍵存在,則用如今的值替換原有值,返回原有值對象;
* 若是鍵不存在則存入鍵值對,返回null */
*/ V put(K key, V value); /* 移除指定鍵對應的值。若是存在鍵,則移除,並返回移除值對象;反之,則返回null */ V remove(Object key); /* 複製另外一張映射表元素到本映射表中 */
void putAll(Map<? extends K, ? extends V> m); /* 基本同Collection,清空映射表 */ void clear(); /* 得到鍵的Set集合 */ Set<K> keySet(); /* 得到值的Collection集合 */ Collection<V> values(); /* 得到鍵值對的Set集合 */ Set<Map.Entry<K, V>> entrySet(); /* 內部接口 Entry<K,V> */ interface Entry<K,V> { /* 獲取鍵值對的鍵 */ K getKey(); /* 獲取鍵值對的值 */ V getValue(); /* 設置鍵值對的值 */ V setValue(V value); /* 比較entry(鍵值對) */ boolean equals(Object o); /* 生成entry對象的hash值 */ int hashCode(); } /* 比較映射表 */ boolean equals(Object o); /* 生成映射表對象的hash碼*/ int hashCode(); }

深刻理解碼源測試

對象比較好基友:equals(Object obj)hashcode()this

Map<K, V>接口及其內部接口Entry<K, V>都有這倆方法。如此設計,目的就是規範其實現子類,要求子類必須重寫Object類的這倆方法,從而完成映射表這種數據結構的既定思想。spa

boolean equals(Object o);   // 對象比較 int hashCode();             // 哈希碼

集合框架通用方法:size()isEmpty()線程

集合框架(包括Collection接口及其子接口ListSetMap接口)內部維護一個size屬性,其描述用戶提供可操縱元素數量,經過size()方法向用戶提供可見性,經過isEmpty()方法向用戶說明是否集合中仍存在其可操縱的元素。

int size();                // 元素保有量 boolean isEmpty();         // 元素數量是否爲0 

鍵、值存在性判斷:containsKey(Object key)containsValue(Object value)

boolean containsKey(Object key);       // 映射表是否包含指定鍵的元素 boolean containsValue(Object value);   // 映射表是否包含指定值得元素

映射表增刪查改:

增、改  V put(K key, V value)  V putAll(Map<? extends K, ? value V> m)

刪        V remove(Object key)   void clear()

查        V get(Object key)

V put(K key, V value)    // 放入或替換指定key的鍵值對  
V putAll(Map<? extends K, ? value V> m)    // 將另外一映射表全部元素放入本映射表
V remove(Object key)     // 移除指定key的鍵值對 
V get(Object key)        // 獲取指定key的鍵值對 void clear()             // 清除全部映射表元素 
package com.forget406.study;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class MapStudy {
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) {
        /* JDK 1.7容許後面的尖括號內不寫泛型變量 */
        Map<String, Coordinate> rect = 
            new HashMap<>();
        rect.put("point A", new Coordinate(0, 0));
        rect.put("point B", new Coordinate(1, 0));
        rect.put("point C", new Coordinate(1, 1));
        rect.put("point D", new Coordinate(0, 1));
        
        Map<String, Coordinate> line =
            new TreeMap<String, Coordinate>();
        line.put("point A", new Coordinate(0, 0));
        line.put("point B", new Coordinate(3, 3));
        
        /***** 實驗測試部分  *****/
        System.out.println(rect);  // output rectangle
        System.out.println(line);  // output line
        System.out.println(rect.put("point D", new Coordinate(2, 2)));  // (0, 1)
        System.out.println(rect.get("point C"));  // (1,1) 
        System.out.println(rect.get("point E"));  // null
        rect.putAll(line);
        System.out.println(rect); 
        System.out.println(line.remove("point C"));  // null
        System.out.println(line.remove("point A"));  // (0, 0)    
    }

}

/**
 * 座標類
 * 
 * @author forget406
 * @since  09/08/2016
 * @param <T> 泛型參數
 */
class Coordinate<T> {
    private T x;
    private T y;
    
    public Coordinate(T x, T y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((x == null) ? 0 : x.hashCode());
        result = prime * result + ((y == null) ? 0 : y.hashCode());
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Coordinate<T> other = (Coordinate<T>) obj;
        if (x == null) {
            if (other.x != null)
                return false;
        } else if (!x.equals(other.x))
            return false;
        if (y == null) {
            if (other.y != null)
                return false;
        } else if (!y.equals(other.y))
            return false;
        return true;
    }
    
    @Override
    public String toString() {
        return "(" + x + "," + y + ")";
    }
}
{point C=(1,1), point B=(1,0), point A=(0,0), point D=(0,1)}
{point A=(0,0), point B=(3,3)}
(0,1)
(1,1)
null
{point C=(1,1), point B=(3,3), point A=(0,0), point D=(2,2)}
null
(0,0)
程序運行結果 

映射表元素遍歷三種方式(或稱 映射表集合視圖(*)):

  • key遍歷  Set<K> keySet()  返回映射表中全部鍵的視圖。能夠從這個Set中刪除元素,同時也從映射表中刪除了它們。   
  • value遍歷  Collection<V> values() 返回映射表中全部值的視圖。能夠從這個Collection中刪除元素,同時也從映射表中刪除了它們。
  • Entry(key-value對)遍歷  Set<Map.Entry<K, V>> entrySet() 返回Map.Entry對象Set集的視圖,即映射表中的鍵/值對。能夠從這個集合中刪除元素,同時也從映射表中刪除了它們。

注意:雖然能夠從這三種遍歷方式遍歷得到的視圖集合中刪除元素,可是均不可以在其中添加元素。

Set<K> keySet();                   // 元素鍵視圖集
Collection<V> values();            // 元素值視圖集
Set<Map.Entry<K, V>> entrySet();   // 元素鍵/值對視圖集

Map接口的內部接口,即Entry<K, V>接口,起封裝鍵值對的做用。經過鍵值對遍歷Map時,能夠經過Entry接口提供的方法獲取相應的鍵、值,以及設置鍵值對的值。

K getKey()   得到鍵值對的鍵

V getValue()  得到鍵值對的值

V setValue(V value)   設置鍵值對的值,同時映射表的內容也同時更新(*)

K setKey(K key)   但沒有提供設置鍵值對鍵的方法,why? 鍵值對的鍵是鍵值對的索引,改變鍵不具備實際意義,並且改變以後會引發一系列諸如hash值改變等問題。

/* Entry<K, V>接口,屬於內部接口 */
interface
Entry<K,V> { K getKey(); // 得到鍵值對的鍵 V getValue(); // 得到鍵值對的值 V setValue(V value); // 設置鍵值對的值 /* 前面已經論述過 */ boolean equals(Object o); int hashCode(); }
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MapStudy {
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) {
        Map<String, Coordinate> rect = 
            new HashMap<String, Coordinate>();
        rect.put("point A", new Coordinate(0, 0));
        rect.put("point B", new Coordinate(1, 0));
        rect.put("point C", new Coordinate(1, 1));
        rect.put("point D", new Coordinate(0, 1));
        
        /***** 實驗測試代碼   *****/
        Set<Entry<String, Coordinate>> pos = rect.entrySet();
/* 最好判斷一下是否爲null */
if (pos != null) { for(Entry<String, Coordinate> p : pos) { System.out.println(p.getKey()+ "=" +p.getValue()); // 得到鍵、值 p.setValue(new Coordinate("?", "?")); // 設置對應Entry的值 } System.out.println(rect); // 更新Entry後,原來映射表內容也隨之更新 } } } /** * 座標類 * * @author forget406 * @since 09/08/2016 * @param <T> 泛型參數 */ class Coordinate<T> { private T x; private T y; public Coordinate(T x, T y) { this.x = x; this.y = y; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((x == null) ? 0 : x.hashCode()); result = prime * result + ((y == null) ? 0 : y.hashCode()); return result; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Coordinate<T> other = (Coordinate<T>) obj; if (x == null) { if (other.x != null) return false; } else if (!x.equals(other.x)) return false; if (y == null) { if (other.y != null) return false; } else if (!y.equals(other.y)) return false; return true; } @Override public String toString() { return "(" + x + "," + y + ")"; } }
point C=(1,1)
point B=(1,0)
point A=(0,0)
point D=(0,1)
{point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
程序測試結果

若是讀者須要進一步瞭解Java的內部接口機制,能夠閱讀另外一篇文章:《Java高級特性(二)內部接口》

Map遍歷方式

Map遍歷做爲文章體系中極爲重要的一塊內容,從上面模塊中抽出來單獨總結。

Iterator<E>接口實現

這種實現方式相對來講比較雞肋,反正遍歷Map我不多用到的。

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MapStudy {
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) {
        Map<String, Coordinate> rect = 
            new HashMap<String, Coordinate>();
        rect.put("point A", new Coordinate(0, 0));
        rect.put("point B", new Coordinate(1, 0));
        rect.put("point C", new Coordinate(1, 1));
        rect.put("point D", new Coordinate(0, 1));
        
        /***** Iterator<E>方式遍歷代碼   *****/
        // 遍歷key,獲取key
        Set<String> keys = rect.keySet();
        Iterator<String> it1 = keys.iterator();
        while(it1.hasNext()) {  //
            String key = it1.next();  //
            System.out.println(key);
            // it1.remove();  刪
        }
        
        // 遍歷value,獲取value
        Collection<Coordinate> values = rect.values();
        Iterator<Coordinate> it2 =  values.iterator();
        while(it2.hasNext()) {
            Coordinate value = it2.next();
            System.out.println(value);
        }
                
        // 遍歷key-value對,獲取key、value,設置value
        Set<Entry<String, Coordinate>> entries = rect.entrySet();
        Iterator<Entry<String, Coordinate>> it3 = entries.iterator();
        while(it3.hasNext()) {
            Entry<String, Coordinate> entry = it3.next();
            String key = entry.getKey();
            System.out.println(key);
            Coordinate value = entry.getValue();
            System.out.println(value);
            entry.setValue(new Coordinate("?", "?"));
        }
        System.out.println(rect);
    }
}

/**
 * 座標類
 * 
 * @author forget406
 * @since  09/08/2016
 * @param <T> 泛型參數
 */
class Coordinate<T> {
    private T x;
    private T y;
    
    public Coordinate(T x, T y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((x == null) ? 0 : x.hashCode());
        result = prime * result + ((y == null) ? 0 : y.hashCode());
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Coordinate<T> other = (Coordinate<T>) obj;
        if (x == null) {
            if (other.x != null)
                return false;
        } else if (!x.equals(other.x))
            return false;
        if (y == null) {
            if (other.y != null)
                return false;
        } else if (!y.equals(other.y))
            return false;
        return true;
    }
    
    @Override
    public String toString() {
        return "(" + x + "," + y + ")";
    }
}
point C
point B
point A
point D
(1,1)
(1,0)
(0,0)
(0,1)
point C
(1,1)
point B
(1,0)
point A
(0,0)
point D
(0,1)
{point C=(?,?), point B=(?,?), point A=(?,?), point D=(?,?)}
程序運行結果

for-each實現

在JDK1.5版本引入for-each(由JVM維護,內部實現也是經過迭代器實現)後,上面那種有點原始社會的感受。因此通常我都是使用for-each遍歷Map

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

public class MapStudy {
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public static void main(String[] args) {
        Map<String, Coordinate> rect = 
            new HashMap<String, Coordinate>();
        rect.put("point A", new Coordinate(0, 0));
        rect.put("point B", new Coordinate(1, 0));
        rect.put("point C", new Coordinate(1, 1));
        rect.put("point D", new Coordinate(0, 1));
        
        /***** for-each方式遍歷代碼   *****/
        // 遍歷key,獲取key
        Set<String> keys = rect.keySet();
        for(String key : keys) {
            System.out.println(key);
        }
        
        // 遍歷value,獲取value
        Collection<Coordinate> values = rect.values();
        for(Coordinate value : values) {
            System.out.println(value);
        }
                
        // 遍歷key-value對,獲取key、value,設置value
        Set<Entry<String, Coordinate>> entries = rect.entrySet();
        for(Entry<String, Coordinate> entry : entries) {
            String key = entry.getKey();
            System.out.println(key);
            Coordinate value = entry.getValue();
            System.out.println(value);
            entry.setValue(new Coordinate("?", "?"));
        }
        System.out.println(rect);
    }
}

/**
 * 座標類
 * 
 * @author forget406
 * @since  09/08/2016
 * @param <T> 泛型參數
 */
class Coordinate<T> {
    private T x;
    private T y;
    
    public Coordinate(T x, T y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((x == null) ? 0 : x.hashCode());
        result = prime * result + ((y == null) ? 0 : y.hashCode());
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Coordinate<T> other = (Coordinate<T>) obj;
        if (x == null) {
            if (other.x != null)
                return false;
        } else if (!x.equals(other.x))
            return false;
        if (y == null) {
            if (other.y != null)
                return false;
        } else if (!y.equals(other.y))
            return false;
        return true;
    }
    
    @Override
    public String toString() {
        return "(" + x + "," + y + ")";
    }
}

Map體系結構

Map接口的實現類大體分爲兩大類:

通用映射表類  

HashMap(散列映射表)、TreeMap(二叉樹映射表)

專用映射表類  

IdentityHashMap(標識散列映射表)、WeakHashMap(弱散列映射表)、LinkedHashMap(連接散列映射表)、EnumMap(枚舉映射表)等。

固然,上面只是比較粗略地分類,後續文章將進一步深刻分析Map相關接口、抽象類、實現類的內部機制,以及其於Set集的對應關係。

Map相關問題思考

思考1:標識符命名單複數

這個問題不光光是Map有,整個Java語言在設計時都是採用這種思路。這種寫法歸根結底仍是因爲英語國家語言語法習慣,值得咱們在平時命名的時候注意。

boolean containsKey(Object key);

若是換成我習慣的命名法則,讓我來設計底層代碼,可能就寫成

boolean containKey(Object key);

boolean isContainKey(Object key);

真的是一個大寫的囧 o(╯□╰)o  和大牛的差距就體現出來了。

對於設計方法標識符時大體應該遵循:

一、若是方法用於判斷,即返回值是boolean

boolean isAb(Ab是名詞)  // 判斷對象是Ab嗎?
boolean AbsCd(Abs是動詞單數,Cd是名詞) // 判斷對象Abs了Cd?好比上面的ContainsKey

二、通常的方法,規則是動詞開頭(具體判斷是否加單數),後能夠加名詞或者連詞(Of、As等)

思考2:Map視圖

視圖(views)實際上是集合框架(collection frame)所共有的語言特性。

關於視圖的內容,會單獨整理總結成文,這裏就是做爲一塊須要重點理解的內容提出來。

Map涉及到視圖的內容:

Set<K> keySet();                   // 元素鍵視圖集
Collection<V> values();            // 元素值視圖集
Set<Map.Entry<K, V>> entrySet();   // 元素鍵/值對視圖集
/** Entry<K, V>接口,屬於內部接口 */
interface Entry<K,V> { K getKey(); // 得到鍵值對的鍵 V getValue(); // 得到鍵值對的值 V setValue(V value); // 設置鍵值對的值 }  

思考3:Map遍歷須要用Collection(集合)和Set(集)的緣由分析

回頭看前面Map接口源代碼,你會發現它並無寫成

public interface Map<K, V> extends Iterable<E>

而是簡單地寫成

public interface Map<K, V>

也就是說,Map接口其實是一個頂層接口

相比較Collection<T>接口繼承更頂層的Iterable<T>(擁有iterator()方法,以及for-each使用必須繼承的接口),Map接口顯得較爲特殊。

爲何Map遍歷必須藉助Collection或Set呢?

假設如今Map接口源代碼寫成

public interface Map<K, V> extends Iterable<E>

那麼問題來了,該怎麼遍歷Map呢?如今有key、value兩個泛型參數,固然咱們能夠將這兩個泛型參數當作一對Entry,不過若是我須要單獨遍歷key或者value呢?沒法單獨經過Map實現。這就會顯得功能太單調。

與其這樣,不如一不作二不休,將Map<K, V>接口的遍歷查詢結果作成三種Set集或Collection集合視圖。

Map<K, V>的K不可以重複,選擇合適的數據結構Set儲存。

Map<K, V>的V能夠重複,選擇合適的數據結構Collection儲存。固然我我的認爲List儲存也是能夠的,只不過設計者選擇更加穩妥、保守的方式而已。

思考4:增刪查改方法都有泛型返回值V(映射表值)

這一點其實也沒琢磨太清楚,多是設計者認爲這麼定義方法更加人性化。好比:

V remove(Object o) 刪除映射表中指定鍵元素,若是刪除則返回刪除元素的值對象,反正則返回null。這樣咱們還可以看看到底刪除值對不對。

相關文章
相關標籤/搜索