爲何equals(Object o)相等,hashCode()必須相等

首先,int hashCode();是爲了支持哈希表類的如HashMapHashTable之類的底層使用了哈希表的類。java

Java Object規範中int hashCode()方法的約定類容有三個:數組

(1)              只要對象的equals方法所用到的信息沒有修改,那麼hashCode方法必須始終如一的返回一個同一整數,在同一應用程序中屢次執行中,每一次執行能夠不同。數據結構

(2)              若是兩個對象的equals方法想到,那麼每個對象單獨調用hashCode返回的數字必須相等。ide

(3)              若是兩個對象的equals方法不相等,hashCode方法的返回值能夠相等,給不一樣的對象返回不一樣的值能夠提升哈希表的性能。函數

下面咱們以HashMap爲例來看一看爲何要作這樣的約定。性能

下圖是HashMap的底層數據結構:this

圖像來自http://zhangshixi.iteye.com/blog/672697spa

經過源碼和上面的示意圖咱們能夠知道HashMap的底層是一個數組,數組的每個元素是一個Entry組織的鏈表。下面是HashMappublic V put(K key, V value)方法的源代碼(1.7):code

 

咱們觀察到被標記的兩條語句,首先是經過hash(Object key)方法獲得一個hash值,而後經過indexFor方法定位到hash值在數組中的位置。下面是hash(Object key)的源碼。咱們能夠看出它調用了KeyhashCode()函數。對象

int indexFor(int h, int length)的源碼:
  static int indexFor(int h, int length) {
        return h & (length-1);
    }


從上面的內容咱們知道咱們用HashMap存儲時是和Keyhash值相關的。若是hash值相同,那麼定位數組的位置也相同(由於indexFor的返回值只和hash值和數組長度length有關,而數組的length只會在重散列時變化)

從前面咱們知道數組的每個元素都是Entry的鏈表,若是每一次hash值都相同,那麼每一次都定位到數組的相同位置,那麼鏈表就會很長,處理的時間也會很長。

hash值是和KeyhashCode方法相關的,因此咱們就能夠理解hashCode的第三條約定了,給不一樣對象產生不一樣的hashCode能夠提升哈希表的性能。

咱們如今再來看一看爲何若是兩個對象的equals方法相等,那麼每個對象單獨調用hashCode方法必須返回相同的返回值。首先咱們仍是先看一看HashMap取得的源代碼:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);
 
        return null == entry ? null : entry.getValue();
    }

 

咱們能夠發現是可逆的,仍是經過hash方法獲得hash值,再經過indexFor方法定位元素在數組中的位置。想想若是咱們兩個對象的equals方法相等,而hashCode方法的值能夠不相等,那是否是就意味着兩個邏輯上相同的對象能夠放在不一樣的位置。那麼是否是就意味着咱們經過邏輯上相等的Key查找的是不一樣的地址(對象)。雖然這並無什麼錯誤,可是並非咱們實現HashMap的目的。下面經過一個例子簡單的看一看會出現什麼問題。

public class User
{
       private long id;
       private String name;
       private String address;
       private long phone;
       @Override
       public boolean equals(Object obj)
       {
              if(obj == this)
                     return true;
              if(! (obj instanceof User))
                     return false;
              User user = (User) obj;
              return (user.id == id && user.phone == phone && name.equals(user.name) && address.equals(user.address));
       }
       
       public User(long id, String name, String address, long phone)
       {
              super();
              this.id = id;
              this.name = name;
              this.address = address;
              this.phone = phone;
       }
 
       public long getId()
       {
              return id;
       }
       public void setId(long id)
       {
              this.id = id;
       }
       public String getName()
       {
              return name;
       }
       public void setName(String name)
       {
              this.name = name;
       }
       public String getAddress()
       {
              return address;
       }
       public void setAddress(String address)
       {
              this.address = address;
       }
       public long getPhone()
       {
              return phone;
       }
       public void setPhone(long phone)
       {
              this.phone = phone;
       }
       
       public static void main(String[] args)
       {
              HashMap<User, String> map = new HashMap<User,String>();
              map.put(new User(1,"tom","china",13888888888L),"hello world");
              System.out.println(map.get(new User(1,"tom","china",13888888888L)));     
       }      
}

咱們的本意是經過User來查找hello world,可是並無如咱們所願對嗎?爲何呢?由於咱們重寫了Userequals方法,可是沒有重寫hashCode方法,因此用的是繼承自Object類的hashCode方法。由於是用new因此地址並不同,hashCode的值天然也就不相同了。因此定位到了其餘的位置,什麼都沒有找到返回null

相關文章
相關標籤/搜索