equals和hashCode方法詳解

一、初識equals()和hashCode()方法

 

1、首先需要明確知道的一點是:hashCode()方法和equals()方法是在Object類中就已經定義了的,所以在java中定義的任何類都會有這兩個方法。原始的equals()方法用來比較兩個對象的地址值,而原始的hashCode()方法用來返回其所在對象的物理地址,下面來看一下在Object中的定義:

    public boolean equals(Object obj) 
    {
        return (this == obj);  //比較的是兩個對象的地址是否相同
    }

hashCode:

public native int hashCode();//對應一個本地實現

2、如果定義了一個類A,只要它沒有重寫這兩個方法,這兩個方法的意義就是如上面所述。請看下面的示例:

示例一

 

class Rect
{
	int x;
	int y;
	public Rect(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
}
public class Temp
{
	public static void main(String[] args)
	{
		Rect r1 = new Rect(1,2);
		Rect r2 = new Rect(1,2);
		System.out.println("r1.equals(r2)爲:" + r1.equals(r2));
		System.out.println("r1的hashCode爲:" + r1.hashCode());
		System.out.println("r2的hashCode爲:" + r2.hashCode());
	}
}
運行結果:

r1.equals(r2)爲:false   //二者內存地址不同,即不是同一對象
r1的hashCode爲:319454176
r2的hashCode爲:357218532

二、equals()和hashCode()的重寫

 

前面說到,因爲這兩個方法都是在Object類中定義,所以java中定義的所有類都會有這兩個方法,並且根據實際需要可以重寫這兩個方法,當方法被重寫後,他們所代表的意義就會發生變化,看下面的代碼:

示例二

	 public static void main(String args[])
	 {
	  String s1=new String("abc");
	  String s2=new String("abc");
	  System.out.println(s1.equals(s2));//輸出true,因爲String類重寫了equals()方法,雖然s1和s2此時指向的地址空間不同,它比較的不再是地址而是String的內容, 此時s1和s2都是"abc",故返回true
	  System.out.println(s1.hashCode());
	  System.out.println(s2.hashCode());//你會發現s1和s2的hashCode相同,因爲String同樣重寫了hashCode()方法,返回的不再是地址,而是根據具體的字符串算出的一個值
	 }
運行結果:

true
96354
96354

 三、equals()和hashCode()方法是怎樣被重寫的

1、我們來看一下String類中這兩個方法的定義:

equals()方法:

	    public boolean equals(Object anObject)
	    {
	        if (this == anObject) //首先比較兩個對象地址;若相同,必是同一對象 
	        {
	            return true;
	        }
	        if (anObject instanceof String) 
	        {
	            String anotherString = (String) anObject;
	            int n = value.length;
	            if (n == anotherString.value.length) 
	            {
	                char v1[] = value;
	                char v2[] = anotherString.value;
	                int i = 0;
	                while (n-- != 0) 
	                {
	                    if (v1[i] != v2[i])   //比較String中每個字符
	                            return false;
	                    i++;
	                }
	                return true;
	            }
	        }
	        return false;
	    }
}

hashCode()方法:

	    public int hashCode() {
	        int h = hash;
	        if (h == 0 && value.length > 0) {
	            char val[] = value;

	            for (int i = 0; i < value.length; i++) {
	                h = 31 * h + val[i];     //根據String中每個字符確定hashCode;若兩個字符串的內容相同,hashCode便相同
	            }
	            hash = h;
	        }
	        return h;
	    }

2、Integer中兩方法的定義:

equals()方法:

    public boolean equals(Object obj) {
        if (obj instanceof Integer) {
            return value == ((Integer)obj).intValue();
        }
        return false;
    }
hashCode方法:

    public int hashCode() {
        return value;
    }

3、小結:

8中基本數據類型的hashCode方法直接就是數值大小,String的hashCode是根據字符串內容計算得的。

8中基本數據類型的equals方法直接比較值,String的equals方法比較的是String的內容。

四、HASH算法簡介:

由於記錄在線性表中的存儲位置是隨機的,和關鍵字無關,因此在查找關鍵字等於給定值的記錄時,需將給定值和線性表中的關鍵字逐個進行比較,查找的效率基於歷經

歷經比較的關鍵字的個數。hash表就是爲了實現在記錄的關鍵字和其存 儲位置之間建立一個確定的函數關係f,即將關鍵字爲key的記錄存儲在f(key)的位置上,這樣在記錄中查找關鍵字時,只需要根據該關鍵字先計算出其在整個記錄中的位置便可找到,不必挨個遍歷了。將hash算法應用到HashSet集合中提高在集合中查找元素的效率。這種方式將集合分成若干個存儲區域,每個對象存儲一個哈希碼,然後將哈希碼分組,每組對應一個存儲區域,根據一個對象的哈希碼就可以確定該對象的存儲區域(採用hash函數來顯示,例如:假設有n個存儲區域,利用哈希碼與n相除的餘數來確定對象存儲區域,若存儲區域相同,便進行再散列)。

Object類中定義了一個hashCode方法來返回每個對象的哈希碼,然後根據哈希碼找到相應的存儲區域,最後取得該區域內的每個元素與該對象就行equals比較,這樣

就不用遍歷集合中的所有元素就可以得出結論,可見HashSet具有很好的檢索性能。但是,HashSet存儲對象的效率略低一些,因爲向HashSet中添加一個元素時,要先計算該對象的哈希碼,並根據哈希碼確定該對象要存儲的存儲區域。爲了保證一個類的每個對象都能在HashSet中正常存儲,要求該類的實例對象在equals方法比較相同時,它們的hashCode必須也是相同的。即:obj1.equals(obj2)爲true的話,obj1.hashCode()==obj2.hashCode()也爲true。換句話說,當我們重寫一個類的equals方法時,必須重寫它的hashCode方法。如果不重寫hashCode方法的話,Object對象中的hashCode方法返回的始終是一個對象的hash地址,而這個地址是永遠不相等的,這時,即使重寫了equals方法也是沒用的,因爲如果兩個對象的hashCode不相等的話,這兩個對象就會被分到不同的存儲區域去了,自然就沒機會用equals方法進行比較了。


五、equals()和hashCode()方法的應用

HashSet集合:

Hashsetjava中一個非常重要的集合類,Hashset中不能有重複的元素,當一個元素添加到集合中的時候,Hashset判斷元素是否重複的依據是這樣的:

1)判斷兩個對象的hashCode是否相等 
   如果不相等,認爲兩個對象也不相等,完畢 
   如果相等,轉入2) 
  (這一點只是爲了提高存儲效率而要求的,其實理論上沒有也可以,但如果沒有,實際使用時效率會大大降低,所以我們這裏將其做爲必需的)
2)判斷兩個對象用equals運算是否相等 
   如果不相等,認爲兩個對象也不相等 
   如果相等,認爲兩個對象相等(equals()是判斷兩個對象是否相等的關鍵
  爲什麼是兩條準則,難道用第一條不行嗎?不行,因爲前面已經說了,hashcode()相等時,equals()方法也可能不等,所以必須用第2條準則進行限制,才能保證加入的爲非重複元素。

示例三

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		if(obj == null)
			return false;
		if(this.getClass() != obj.getClass())
			return false;
		final RectObject other = (RectObject)obj;
		if(this.x != other.x)
			return false;
		if(this.y != other.y)
			return false;
		return true;
	}	
}
public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("當前HashSet中元素個數:" + hashSet.size());
	}
}
運行結果:
當前HashSet中元素個數:2
分析:

我們重寫了Object中的hashCode和equals方法,當兩個對象的x、y值都相等時,它們的hashCode值是相等的,並且equals比較的結果爲true。
先添加rect1到HashSet中;在添加rect2時,由於hashCode不等,因而可以添加;添加rect3時,rect3與rect1的hashCode值相等,因而進行equals的比較,爲true,不會添加;在添加rect4時,由於與rect1相同,不會添加。
示例四(即不重寫hashCode方法)

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
//	@Override
//	public int hashCode()
//	{
//		final int prime = 31;
//		int result = 1;
//		result = prime * result + x;
//		result = prime * result + y;
//		return result;
//	}
	@Override
	public boolean equals(Object obj)
	{
		if(this == obj)
			return true;
		if(obj == null)
			return false;
		if(this.getClass() != obj.getClass())
			return false;
		final RectObject other = (RectObject)obj;
		if(this.x != other.x)
			return false;
		if(this.y != other.y)
			return false;
		return true;
	}
}

public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("當前HashSet中元素個數:" + hashSet.size());
	}
}
運行結果:

當前HashSet中元素個數:3
分析:先將rect1添加至HashSet中;添加rect2,rect3時,三者的hashCode均不相同,因而全部添加至集合中;因rect1 == rect1,不會添加;因而集合中有rect1、rect2、rect3三個對象。
示例五(將重寫的equals方法改爲直接返回false)

class RectObject
{
	int x;
	int y;
	public RectObject(int x,int y)
	{
		this.x = x;
		this.y = y;
	}
	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + x;
		result = prime * result + y;
		return result;
	}
	@Override
	public boolean equals(Object obj)
	{
//		if(this == obj)
//			return true;
//		if(obj == null)
//			return false;
//		if(this.getClass() != obj.getClass())
//			return false;
//		final RectObject other = (RectObject)obj;
//		if(this.x != other.x)
//			return false;
//		if(this.y != other.y)
//			return false;
//		return true;
		return false;
	}
}

public class Test
{
	public static void main(String[] args)
	{
		HashSet<RectObject> hashSet = new HashSet<> ();
		RectObject rect1 = new RectObject(1, 2);
		RectObject rect2 = new RectObject(3, 4);
		RectObject rect3 = new RectObject(1, 2);
		hashSet.add(rect1);
		hashSet.add(rect2);
		hashSet.add(rect3);
		hashSet.add(rect1);
		System.out.println("當前HashSet中元素個數:" + hashSet.size());
	}
}
運行結果:

當前HashSet中元素個數:3
分析:首先添加rect1到集合中;比較rect2與rect1的hashCode不同,將rect2添加至集合;rect3與rect1的hashCode相同,equals比較爲false,將rect3添加至集合;然後是rect1,rect1與rect1的hashCode相同,equals比較爲false,所以應該可以添加第二個rect1到集合中,結果應爲4,但事實上卻是3,下面來分析:

先看HashSet中add方法源碼:

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
HashSet時基於HashMap實現的,我們來看HashMap中put的源碼

    public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);
        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;
    }
主要看if判斷條件:

顯示判斷兩對象hashCode是否相等,不等的話,直接跳過後面的判斷;相等的話,再來比較兩個對象是否相等或者這兩個對象的equals方法,因爲是或運算,只要一個成立即可。這樣,我們剛纔在放最後一個rect1時,由於rect1 == rect1,所以最後一個rect1並沒有放進去,所以集合大小爲3。

參考文檔:http://blog.csdn.net/jiangwei0910410003/article/details/22739953



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章