1. 程式人生 > >JAVA hashcode和equals為何要同時重寫

JAVA hashcode和equals為何要同時重寫

hashCode是雜湊碼,用來快速查詢用的 你看到的那一串的格式如下,比如一個[email protected] "@ "前面的是你的類名,後面的就是雜湊碼的16進製表示。hashCode的查詢原理:先有很多個數組,然後你要往容器裡面放東西,比如hashSet,如放一個String=“Hello”,那麼計算機會先計算雜湊碼,然後放入相應的陣列中,陣列的索引就是從雜湊嗎計算來的,然後再裝入數組裡的容器裡,比如List.這就相當於把你要存的資料分成了幾個大的部分,然後每個部分存了很多值, 你查詢的時候先查大的部分,再在大的部分裡面查小的,這樣就比先行查詢要快

大家都知道,集合是一種容器,生活中容器就是用來裝東西的,那麼在計算機語言中的容器就是用來裝資料的。在生活中,我們有各式各樣的容器,不同容器有不同的優缺點。比如可以用籃子來裝饅頭,也可以用塑料袋裝饅頭。用籃子來裝呢比較乾淨,如果蓋上布就能保溫,而用塑料袋既便宜又輕便,大家買饅頭時,由賣家提供。在Java類庫中提供了很多衫的集合類,他們是通過介面(Collection、Map)組織起來的。這些集合各有特點,List中的資料有索引,訪問方便,Set 中的資料不允許重複,Map中的資料是以鍵-值對的形式進行儲存的。由於這不是這篇文章的重點,所以不在過多解釋。

對於Set介面的實現類HashSet,它是按照雜湊演算法來存取集合中的物件,並且因為其繼承了Set介面,所以不允許插入相同的資料。那麼它如何來保證不插入相同的資料,這就要使用到equals()和hashCode()方法了。在我們往HashSet裡面新增物件(add()方法裡的引數都是物件)的時候,在Add()的方法內部,它首先呼叫該物件的hashCode()方法(hashCode方法用來計算該物件的雜湊碼),如果返回的雜湊碼與集合已存在物件的雜湊碼不一致,則add()方法認定該物件沒有與集合中的其它物件重複,那麼該物件將被新增進集合中。如果hashCode()方法返回的雜湊碼與集合已存在物件的雜湊碼一致,那麼將呼叫該物件的equals方法,進一步判斷其是否為同一物件。之所以在進行了hashcode(雜湊碼)的比較後,又呼叫equals()方法進行比較,是因為雖然HashSet採用的是通過hashcode來區分物件,但是在java中hashcode會重碼(即不同的物件,其hashcode可能會相同,可以參看附例1.1)。通過hashCode()和equals()方法就能快速且準確的判斷在集合中是否存在與新增物件相同的物件。

在如下的例子中,我向集合set1中添加了兩個相同的物件1,因為1為類Integer的物件,所以當我呼叫add()方法插入第2個1時,它會自動呼叫Integer物件的hashCode()和equals()方法。Integer物件的這兩個方法是重寫其父類Object的。在JavaAPI中Integer的hashCode()方法的返回值是這樣描述的:該物件的雜湊碼值,它的值即為該 Integer物件表示的基本 int型別的數值。很顯示第一次新增的1與第2新增的1它們的hashCode()是相同的。Integer的equals()方法是這樣描述的:比較此物件與指定物件,當且僅當引數不為 null,並且是一個與該物件包含相同 int值的Integer物件時,結果為 true。1與1的int值也是相同的,所以它們equals()為true。最終判定他們為相同物件,無法插入第2個1。

import java.util.HashSet;

import java.util.Iterator;

import java.util.Set;

public class TestHashSet1 {

public static void main(String[] args) {

Set<Integer> set = new HashSet<Integer>();

set.add(1);

set.add(1);

Iterator<Integer> it = set.iterator();

while(it.hasNext()){

System.out.println(it.next());

}

}

}

下面我們來談一下hashCode()與equals()方法的重寫。首先我們先看一個例子。

import java.util.HashSet;

import java.util.Iterator;

import java.util.Set;

class Person {

private String name;

private int id;

Person(String name,int id) {

this.name = name;

this.id = id;

}

public void setName(String name){

this.name = name;

}

public String getName(){

return name;

}

public void setId(int id){

this.id = id;

}

public int getId(){

return id;

}

}

public class TestHashSet2 {

public static void main(String[] args) {

Person p1 = new Person("chen",1001);

Person p2 = new Person("chen",1001);

Set<Person> set = new HashSet<Person>();

set.add(p1);

set.add(p2);

Iterator<Person> it = set.iterator();

while(it.hasNext()){

System.out.println(it.next().getName());

}

}

}

在上面的例子中我向集合中新增兩個Person物件p1,p2,它們的姓名和id號都是一樣,當我們執行下面的例子時,會發現p1和p2都新增進了集合。這是因為,此時我們沒有重寫Person的hashCode()方法,這時執行add(p2)時,他會呼叫Person父類(即Object)的hashCode()方法,Object的hashCode()是這樣定義的:由 Object 類定義的 hashCode 方法確實會針對不同的物件返回不同的整數(這一般是通過將該物件的內部地址轉換成一個整數來實現的)。我們建立的物件p1和p2他們的內部地址不同,因此返回的雜湊碼也不相同,那麼add()方法執行時就會將p2看成是不同於p1的物件。這顯然不是我們需求的,為了滿足我們的需求,我們則需要重寫hashCode()方法。下面我給出一種重寫hashCode()的方法。

public int hashCode(){

return name.hashCode()+id; //這種寫法重位元速率高,不推薦使用

}

在這個方法中我返回的雜湊值是字串name的雜湊碼(字串的雜湊碼演算法,可查閱JavaAPI)加上id值(Integer物件的hashcode就是其int值)。在Person類中加入上述程式碼後,我們還需要重寫其equals()方法(hashcode會重碼)。程式碼如下:

public boolean equals(Object obj){

if(obj instanceof Person){

Person p = (Person)obj;

return(name.equals(p.name) && id == p.id);

}

return super.equals(obj);

}

此equals()方法的執行過程是這樣的,當我使用add()方法時,首先進行hashCode()的計算,因為p2的hashcode與集合中物件的hashcode有重複(p1的),所以在此時呼叫equals()方法判斷他們是否真的相等。在此例中obj的值即為p1,首先判斷p1是否為Person物件,若是,則行強制轉換成Person型別(傳進來的是Object型別),然後返回name.equals(p.name) && id == p.id 的值。若傳進來的obj不是Person型別,則呼叫父類的equals()方法。

至此,我們在執行上述程式,則只新增進一個物件。

總結如下:hashCode不同時,則必為不同物件。hashCode相同時,根據equlas()方法判斷是否為同一物件。

附1.1:下述程式中,p1與p2的hashCode相同,但並不是同一物件。

import java.util.HashSet;

import java.util.Iterator;

import java.util.Set;

class Person {

private String name;

private int id;

Person(String name,int id) {

this.name = name;

this.id = id;

}

public void setName(String name){

this.name = name;

}

public String getName(){

return name;

}

public void setId(int id){

this.id = id;

}

public int getId(){

return id;

}

public int hashCode(){

return name.hashCode()+id; //這種寫法重位元速率高,不推薦使用

}

public boolean equals(Object obj){

if(obj instanceof Person){ //

Person p = (Person)obj;

return(name.equals(p.name) && id == p.id);

}

return super.equals(obj);

}

}

public class TestHashSet2 {

public static void main(String[] args) {

Person p1 = new Person("a",1);

Person p2 = new Person("b",0);

Set<Person> set = new HashSet<Person>();

set.add(p1);

set.add(p2);

Iterator<Person> it = set.iterator();

while(it.hasNext()){

System.out.println(it.next().getName());

}

}

}