Java中hashcode()方法的前生與今世

當咱們要學習hashcode()方法時,咱們須要一步一步弄清楚如下這幾個方面:java

1.hashcode()的由來

hashcode是jdk根據對象的地址或者字符串或者數字算出來的int類型的數值,那麼是經過什麼算出來的呢?答案是hashtable(哈希表),哈希表就是根據關鍵碼值(Key value)而直接進行訪問的數據結構。這樣的話可能太過於官方,在這裏說一下個人理解,hashcode就是經過一個函數映射獲得的值,這個函數叫哈希函數,咱們向函數中傳入一個key值,就會獲得一個value值,經過哈希函數獲得的value就是哈希值,即value=f(key)。那麼理解了hashcode以後,咱們就知道hashcode()方法是幹什麼用的了,hashcode()方法就是爲了計算並返回hashcode方法。編程

2.hashcode()的做用

哈希表的主要好處就是它可以提升查找效率。經過查看API咱們能夠發現,hashcode()是Object的方法,也能夠說正常狀況下全部類的對象都夠調用hashcode()方法,在Java中,根據他可以提升查找效率的特色,咱們主要應用在爲了配合基於散列的集合一塊兒正常運行,這樣的散列集合包括HashSet、HashMap以及HashTable。集合中不容許重複的元素存在,那麼當咱們須要加入一個新的元素時,如何去判斷該元素已經存在了呢?也許大多數人都會想到調用equals方法來逐個進行比較,這個方法確實可行。可是若是集合中已經存在一萬條數據或者更多的數據,若是採用equals方法去逐一比較,效率必然是一個問題。此時hashCode方法的做用就體現出來了,當集合要添加新的對象時,先調用這個對象的hashCode方法,獲得對應的hashcode值,實際上在HashMap的具體實現中會用一個table保存已經存進去的對象的hashcode值,若是table中沒有該hashcode值,它就能夠直接存進去,不用再進行任何比較了;若是存在該hashcode值, 就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列其它的地址,因此這裏存在一個衝突解決的問題,這樣一來實際調用equals方法的次數就大大下降了。下面這段代碼是java.util.HashMap的中put方法的具體實現:數據結構

public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
 
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

put方法是用來向HashMap中添加新的元素,從put方法的具體實現可知,會先調用hashCode方法獲得該元素的hashCode值,而後查看table中是否存在該hashCode值,若是存在則調用equals方法從新肯定是否存在該元素,若是存在,則更新value值,不然將新的元素添加到HashMap中。從這裏能夠看出,hashCode方法的存在是爲了減小equals方法的調用次數,從而提升程序效率ide

3.hashcode()方法與equals()方法函數

在這裏須要注意幾個問題。判斷對象相等是否能夠用hashcode()方法,答案是不能夠必須用equals()方法。兩個不一樣對象可能hashcode相等,但兩個不一樣hashcode的對象必定不一樣學習

另外一個注意的問題:設計一個類的時候爲須要重寫equals方法,好比String類,可是千萬要注意,在重寫equals方法的同時,必須重寫hashCode方法。下面看一個只重寫了equals方法的例子:this

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
 
class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  
     
    public void setAge(int age){
        this.age = age;
    }
         
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
 
    public static void main(String[] args) {
         
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
             
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        System.out.println(hashMap.get(new People("Jack", 12)));
    }
}

若是兩個People對象,若是它的姓名和年齡相等,則認爲是同一我的。這段代碼原本的意願是想這段代碼輸出結果爲「1」,可是事實上它輸出的是「null」。spa

雖然經過重寫equals方法使得邏輯上姓名和年齡相同的兩個對象被斷定爲相等的對象(跟String類相似),可是要知道默認狀況下,hashCode方法是將對象的存儲地址進行映射。那麼上述代碼的輸出結果爲「null」就不足爲奇了。緣由很簡單,p1指向的對象和System.out.println(hashMap.get(new People("Jack", 12)));這句中的new People("Jack", 12)生成的是兩個對象,它們的存儲地址確定不一樣。因此在hashmap進行get操做時,由於獲得的hashcdoe值不一樣直接返回null。若是想上述代碼輸出結果爲「1」,很簡單,只須要重寫hashCode方法,讓equals方法和hashCode方法始終在邏輯上保持一致性。設計

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
 
 
class People{
    private String name;
    private int age;
     
    public People(String name,int age) {
        this.name = name;
        this.age = age;
    }  
     
    public void setAge(int age){
        this.age = age;
    }
     
    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return name.hashCode()*37+age;
    }
     
    @Override
    public boolean equals(Object obj) {
        // TODO Auto-generated method stub
        return this.name.equals(((People)obj).name) && this.age== ((People)obj).age;
    }
}
 
public class Main {
 
    public static void main(String[] args) {
         
        People p1 = new People("Jack", 12);
        System.out.println(p1.hashCode());
             
        HashMap<People, Integer> hashMap = new HashMap<People, Integer>();
        hashMap.put(p1, 1);
         
        System.out.println(hashMap.get(new People("Jack", 12)));
    }
}

下面引用一下Effective Java一書: code

  • 在程序執行期間,只要equals方法的比較操做用到的信息沒有被修改,那麼對這同一個對象調用屢次,hashCode方法必須始終如一地返回同一個整數。
  • 若是兩個對象根據equals方法比較是相等的,那麼調用兩個對象的hashCode方法必須返回相同的整數結果。
  • 若是兩個對象根據equals方法比較是不等的,則hashCode方法不必定得返回不一樣的整數。

  對於第二條和第三條很好理解,可是第一條,不少時候就會忽略。在《Java編程思想》一書中的P495頁也有同第一條相似的一段話:

  「設計hashCode()時最重要的因素就是:不管什麼時候,對同一個對象調用hashCode()都應該產生一樣的值。若是在講一個對象用put()添加進HashMap時產生一個hashCdoe值,而用get()取出時卻產生了另外一個hashCode值,那麼就沒法獲取該對象了。因此若是你的hashCode方法依賴於對象中易變的數據,用戶就要小心了,由於此數據發生變化時,hashCode()方法就會生成一個不一樣的散列碼」。

相關文章
相關標籤/搜索