HashMap的key能夠是可變的對象嗎???

  你們都知道,HashMap的是key-value(鍵值對)組成的,這個key既能夠是基本數據類型對象,如Integer,Float,同時也能夠是本身編寫的對象,那麼問題來了,這個做爲key的對象是否可以改變呢?或者說key可否是一個可變的對象?若是能夠該HashMap會怎麼樣?html

可變對象java

  可變對象是指建立後自身狀態能改變的對象。換句話說,可變對象是該對象在建立後它的哈希值(由類的hashCode()方法能夠得出哈希值)可能被改變shell

  爲了能直觀的看出哈希值的改變,下面編寫了一個類,同時重寫了該類的hashCode()方法和它的equals()方法【至於爲何要重寫equals方法能夠看博客:http://www.cnblogs.com/0201zcr/p/4769108.html】,在查找和添加(put方法)的時候都會用到equals方法。數組

  在下面的代碼中,對象MutableKey的鍵在建立時變量 i=10 j=20,哈希值是1291。安全

  而後咱們改變實例的變量值,該對象的鍵 i 和 j 從10和20分別改變成30和40。如今Key的哈希值已經變成1931。ide

  顯然,這個對象的鍵在建立後發生了改變。因此類MutableKey是可變的。函數

  讓咱們看看下面的示例代碼:測試

public class MutableKey {
    private int i;
    private int j;
 
    public MutableKey(int i, int j) {
        this.i = i;
        this.j = j;
    }
 
    public final int getI() {
        return i;
    }
 
    public final void setI(int i) {
        this.i = i;
    }
 
    public final int getJ() {
        return j;
    }
 
    public final void setJ(int j) {
        this.j = j;
    }
 
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + i;
        result = prime * result + j;
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (!(obj instanceof MutableKey)) {
            return false;
        }
        MutableKey other = (MutableKey) obj;
        if (i != other.i) {
            return false;
        }
        if (j != other.j) {
            return false;
        }
        return true;
    }
}

測試:this

public class MutableDemo {
 
    public static void main(String[] args) {
 
        // Object created
        MutableKey key = new MutableKey(10, 20);
        System.out.println("Hash code: " + key.hashCode());
 
        // Object State is changed after object creation.
        key.setI(30);
        key.setJ(40);
        System.out.println("Hash code: " + key.hashCode());
    }
}

結果:spa

Hash code: 1291
Hash code: 1931

   只要MutableKey 對象的成員變量i或者j改變了,那麼該對象的哈希值改變了,因此該對象是一個可變的對象。

HashMap如何存儲鍵值對

  HashMap底層是使用Entry對象數組存儲的,而Entry是一個單項的鏈表。當調用一個put()方法將一個鍵值對添加進來是,先使用hash()函數獲取該對象的hash值,而後調用indexFor方法查找到該對象在數組中應該存儲的下標,假如該位置爲空,就將value值插入,若是該下標出不爲空,則要遍歷該下標上面的對象,使用equals方法進行判斷,若是遇到equals()方法返回真的則進行替換,不然將其插入,源碼詳解可看:http://www.cnblogs.com/0201zcr/p/4769108.html

  查找時只須要查詢經過key值獲取獲取hash值,而後找到其下標,遍歷該下標下面的Entry對象便可查找到value。【具體看下面源碼及其解釋】

在HashMap中使用可變對象做爲Key帶來的問題

  若是HashMap Key的哈希值在存儲鍵值對後發生改變,Map可能再也查找不到這個Entry了

public V get(Object key)   
{   
 // 若是 key 是 null,調用 getForNullKey 取出對應的 value   
 if (key == null)   
     return getForNullKey();   
 // 根據該 key 的 hashCode 值計算它的 hash 碼  
 int hash = hash(key.hashCode());   
 // 直接取出 table 數組中指定索引處的值,  
 for (Entry<K,V> e = table[indexFor(hash, table.length)];   
     e != null;   
     // 搜索該 Entry 鏈的下一個 Entr   
     e = e.next)         //
 {   
     Object k;   
     // 若是該 Entry 的 key 與被搜索 key 相同  
     if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value;  
 }   
 return null;   
}   

  上面是HashMap的get()方法源碼,經過上面咱們能夠知道,若是 HashMap 的每一個 bucket 裏只有一個 Entry 時,HashMap 能夠根據索引、快速地取出該 bucket 裏的 Entry;在發生「Hash 衝突」的狀況下,單個 bucket 裏存儲的不是一個 Entry,而是一個 Entry 鏈系統只能必須按順序遍歷每一個 Entry,直到找到想搜索的 Entry 爲止——若是剛好要搜索的 Entry 位於該 Entry 鏈的最末端(該 Entry 是最先放入該 bucket 中),那系統必須循環到最後才能找到該元素。 

  同時咱們也看到,判斷是否找到該對象,咱們還須要判斷他的哈希值是否相同,假如哈希值不相同,根本就找不到咱們要找的值。

  若是Key對象是可變的,那麼Key的哈希值就可能改變。在HashMap中可變對象做爲Key會形成數據丟失。

  下面的例子將會向你展現HashMap中有可變對象做爲Key帶來的問題。

import java.util.HashMap;
import java.util.Map;
 
public class MutableDemo1 {
 
    public static void main(String[] args) {
 
        // HashMap
        Map<MutableKey, String> map = new HashMap<>();
 
        // Object created
        MutableKey key = new MutableKey(10, 20);
 
        // Insert entry.
        map.put(key, "Robin");
 
        // This line will print 'Robin'
        System.out.println(map.get(key));
 
        // Object State is changed after object creation.
        // i.e. Object hash code will be changed.
        key.setI(30);
 
        // This line will print null as Map would be unable to retrieve the
        // entry.
        System.out.println(map.get(key));
    }
}

輸出:

Robin
null

 

如何解決

  在HashMap中使用不可變對象。在HashMap中,使用String、Integer等不可變類型用做Key是很是明智的。 

  咱們也能定義屬於本身的不可變類

  若是可變對象在HashMap中被用做鍵,那就要當心在改變對象狀態的時候,不要改變它的哈希值了。咱們只須要保證成員變量的改變能保證該對象的哈希值不變便可。

  在下面的Employee示例類中,哈希值是用實例變量id來計算的。一旦Employee的對象被建立,id的值就不能再改變只有name能夠改變,但name不能用來計算哈希值。因此,一旦Employee對象被建立,它的哈希值不會改變。因此Employee在HashMap中用做Key是安全的。

import java.util.HashMap;
import java.util.Map;
 
public class MutableSafeKeyDemo {
 
    public static void main(String[] args) {
        Employee emp = new Employee(2);
        emp.setName("Robin");
 
        // Put object in HashMap.
        Map<Employee, String> map = new HashMap<>();
        map.put(emp, "Showbasky");
 
        System.out.println(map.get(emp));
 
        // Change Employee name. Change in 'name' has no effect
        // on hash code.
        emp.setName("Lily");
        System.out.println(map.get(emp));
    }
}
 
class Employee {
    // It is specified while object creation.
    // Cannot be changed once object is created. No setter for this field.
    private int id;
    private String name;
 
    public Employee(final int id) {
        this.id = id;
    }
 
    public final String getName() {
        return name;
    }
 
    public final void setName(final String name) {
        this.name = name;
    }
 
    public int getId() {
        return id;
    }
 
    // Hash code depends only on 'id' which cannot be
    // changed once object is created. So hash code will not change
    // on object's state change
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + id;
        return result;
    }
 
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Employee other = (Employee) obj;
        if (id != other.id)
            return false;
        return true;
    }
}

輸出

Showbasky
Showbasky

   致謝:感謝您的耐心閱讀!

相關文章
相關標籤/搜索