day05-hashCode理解

感謝作者:https://blog.csdn.net/fenglibing/article/details/8905007
感謝作者:https://www.cnblogs.com/dolphin0520/p/3681042.html
感謝作者:http://blog.csdn.net/chinayuan/article/details/3345559

hashCode理解

以下是關於HashCode的官方文檔定義:

hashcode方法返回該對象的哈希碼值。支持該方法是爲哈希表提供一些優點,例如,java.util.Hashtable 提供的哈希表。 

hashCode 的常規協定是: 
在 Java 應用程序執行期間,在同一對象上多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是對象上 equals 比較中所用的信息沒有被修改。從某一應用程序的一次執行到同一應用程序的另一次執行,該整數無需保持一致。 
如果根據 equals(Object) 方法,兩個對象是相等的,那麼在兩個對象中的每個對象上調用 hashCode 方法都必須生成相同的整數結果。 
以下情況不 是必需的:如果根據 equals(java.lang.Object) 方法,兩個對象不相等,那麼在兩個對象中的任一對象上調用 hashCode 方法必定會生成不同的整數結果。但是,程序員應該知道,爲不相等的對象生成不同整數結果可以提高哈希表的性能。 
實際上,由 Object 類定義的 hashCode 方法確實會針對不同的對象返回不同的整數。(這一般是通過將該對象的內部地址轉換成一個整數來實現的,但是 JavaTM 編程語言不需要這種實現技巧。) 

當equals方法被重寫時,通常有必要重寫 hashCode 方法,以維護 hashCode 方法的常規協定,該協定聲明相等對象必須具有相等的哈希碼。

以上這段官方文檔的定義,我們可以抽出成以下幾個關鍵點:
1、hashCode的存在主要是用於查找的快捷性,如Hashtable,HashMap等,hashCode是用來在散列存儲結構中確定對象的存儲地址的;

2、如果兩個對象相同,就是適用於equals(java.lang.Object) 方法,那麼這兩個對象的hashCode一定要相同;

3、如果對象的equals方法被重寫,那麼對象的hashCode也儘量重寫,並且產生hashCode使用的對象,一定要和equals方法中使用的一致,否則就會違反上面提到的第2點;

4、兩個對象的hashCode相同,並不一定表示兩個對象就相同,也就是不一定適用於equals(java.lang.Object) 方法,只能夠說明這兩個對象在散列存儲結構中,如Hashtable,他們“存放在同一個籃子裏”。

以下兩節文字感謝作者:http://blog.csdn.net/chinayuan/article/details/3345559

怎樣理解hashCode的作用

以 java.lang.Object來理解,JVM每new一個Object,它都會將這個Object丟到一個Hash哈希表中去,這樣的話,下次做 Object的比較或者取這個對象的時候,它會依據對象的hashcode再從Hash表中取這個對象。這樣做的目的是提高取對象的效率。詳細過程是這樣:

1.new Object(),JVM依據這個對象的Hashcode值,放入到相應的Hash表相應的Key上,假設不同的對象確產生了同樣的hash值,也就是發 生了Hash key同樣導致衝突的情況,那麼就在這個Hash key的地方產生一個鏈表,將全部產生同樣hashcode的對象放到這個單鏈表上去,串在一起。
2.比較兩個對象的時候,首先依據他們的 hashcode去hash表中找他的對象,當兩個對象的hashcode同樣,那麼就是說他們這兩個對象放在Hash表中的同一個key上,那麼他們一 定在這個key上的鏈表上。

那麼此時就僅僅能依據Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不同的話,肯定 他們不能equal.

改寫equals時總是要改寫hashCode

java.lang.Object中對hashCode的約定:

  1. 在一個應用程序運行期間,假設一個對象的equals方法做比較所用到的信息沒有被改動的話。則對該對象調用hashCode方法多次,它必須始終如一地返回同一個整數。
  2. 假設兩個對象依據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生同樣的整數結果。
  3. 假設兩個對象依據equals(Object o)方法是不相等的。則調用這兩個對象中任一個對象的hashCode方法。不要求產生不同的整數結果。但假設能不同,則可能提高散列表的性能。

有一個概念要牢記。兩個相等對象的equals方法一定爲true, 但兩個hashcode相等的對象不一定是相等的對象。
所以hashcode相等僅僅能保證兩個對象在一個HASH表裏的同一條HASH鏈上,繼而通過equals方法才幹確定是不是同一對象,假設結果爲true, 則覺得是同一對象在插入。否則覺得是不同對象繼續插入。

Object的代碼:

public String toString () {
	return this.getClass().getName() + “@” + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
	return this == o;
}

public native int hashCode();

在有些情況下,程序設計者在設計一個類的時候爲需要重寫equals方法,比如String類,但是千萬要注意,在重寫equals方法的同時,必須重寫hashCode方法。爲什麼這麼說呢?
看一個例子:

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)));//null
    }
}

在這裏我只重寫了equals方法,也就說如果兩個People對象,如果它的姓名和年齡相等,則認爲是同一個人。
這段代碼本來的意願是想這段代碼輸出結果爲“1”,但是事實上它輸出的是“null”。爲什麼呢?原因就在於重寫equals方法的同時忘記重寫hashCode方法。雖然通過重寫equals方法使得邏輯上姓名和年齡相同的兩個對象被判定爲相等的對象(跟String類類似),但是要知道默認情況下,hashCode方法是將對象的存儲地址進行映射。那麼上述代碼的輸出結果爲“null”就不足爲奇了。
原因很簡單,p1指向的對象和System.out.println(hashMap.get(new People(“Jack”, 12)));這句中的new People(“Jack”, 12)生成的是兩個對象,它們的存儲地址肯定不同。

當上面同樣的代碼,當我們重寫了hashCode方法時,這樣一來的話,輸出結果就爲“1”了。
下面這段話摘自Effective Java一書:

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

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

“設計hashCode()時最重要的因素就是:無論何時,對同一個對象調用hashCode()都應該產生同樣的值。
如果在講一個對象用put()添加進HashMap時產生一個hashCode值,而用get()取出時卻產生了另一個hashCode值,那麼就無法獲取該對象了。
所以如果你的hashCode方法依賴於對象中易變的數據,用戶就要當心了,因爲此數據發生變化時,hashCode()方法就會生成一個不同的散列碼”。

hashCode與equals

再歸納一下就是hashCode是用於查找使用的,而equals是用於比較兩個對象的是否相等的。以下這段話是從別人帖子回覆拷貝過來的:

1.hashcode是用來查找的,如果你學過數據結構就應該知道,在查找和排序這一章有
例如內存中有這樣的位置
0  1  2  3  4  5  6  7  
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,如果不用hashcode而任意存放,那麼當查找時就需要到這八個位置裏挨個去找,或者用二分法一類的算法。
但如果用hashcode那就會使效率提高很多。
我們這個類中有個字段叫ID,那麼我們就定義我們的hashcode爲ID%8,然後把我們的類存放在取得得餘數那個位置。比如我們的ID爲998的餘數爲1,那麼我們就把該類存在1這個位置,如果ID是13,求得的餘數是5,那麼我們就把該類放在5這個位置。這樣,以後在查找該類時就可以通過ID除 8求餘數直接找到存放的位置了。

2.但是如果兩個類有相同的hashcode怎麼辦那(我們假設上面的類的ID不是唯一的),例如9除以817除以8的餘數都是1,那麼這是不是合法的,回答是:可以這樣。那麼如何判斷呢?在這個時候就需要定義 equals了。
也就是說,我們先通過 hashcode來判斷兩個類是否存放某個桶裏,但這個桶裏可能有很多類,那麼我們就需要再通過 equals 來在這個桶裏找到我們要的類。
那麼。重寫了equals(),爲什麼還要重寫hashCode()呢?
想想,你要在一個桶裏找東西,你必須先要找到這個桶啊,你不通過重寫hashcode()來找到桶,光重寫equals()有什麼用啊

最後,我們來看一個具體的示例吧,

public class HashTest {
	private int i;
 
	public int getI() {return i;}
 	public void setI(int i) {this.i = i;}
 
	public int hashCode() {
		return i % 10;
	}
 
	public final static void main(String[] args) {
		HashTest a = new HashTest();
		HashTest b = new HashTest();
		a.setI(1);      b.setI(1);
		Set<HashTest> set = new HashSet<HashTest>();
		set.add(a);     set.add(b);
		System.out.println(a.hashCode() == b.hashCode());//true
		System.out.println(a.equals(b));//false
		System.out.println(set);//[com.ubs.sae.test.HashTest@1, com.ubs.sae.test.HashTest@1]
	}
}

以上這個示例,我們只是重寫了hashCode方法,從上面的結果可以看出,雖然兩個對象的hashCode相等,但是實際上兩個對象並不是相等;,我們沒有重寫equals方法,那麼就會調用object默認的equals方法,是比較兩個對象的引用是不是相同,顯示這是兩個不同的對象,兩個對象的引用肯定是不定的。這裏我們將生成的對象放到了HashSet中,而HashSet中只能夠存放唯一的對象,也就是相同的(適用於equals方法)的對象只會存放一個,但是這裏實際上是兩個對象a,b都被放到了HashSet中,這樣HashSet就失去了他本身的意義了。
此時我們把equals方法給加上:

public class HashTest {
	private int i;
 
	public int getI() {return i;}
 	public void setI(int i) {this.i = i;}
 
	public boolean equals(Object object) {
		if (object == null) {
			return false;
		}
		if (object == this) {
			return true;
		}
		if (!(object instanceof HashTest)) {
			return false;
		}
		HashTest other = (HashTest) object;
		if (other.getI() == this.getI()) {
			return true;
		}
		return false;
	}
 
	public int hashCode() {
		return i % 10;
	}
 
	public final static void main(String[] args) {
		HashTest a = new HashTest();
		HashTest b = new HashTest();
		a.setI(1);     b.setI(1);
		
		Set<HashTest> set = new HashSet<HashTest>();
		set.add(a);    set.add(b);
		System.out.println(a.hashCode() == b.hashCode());//true
		System.out.println(a.equals(b));//true
		System.out.println(set);//[com.ubs.sae.test.HashTest@1]
	}
}

內存地址與hashCode

感謝作者:https://www.cnblogs.com/dolphin0520/p/3681042.html
有些朋友誤以爲默認情況下,hashCode返回的就是對象的存儲地址,事實上這種看法是不全面的,確實有些JVM在實現時是直接返回對象的存儲地址,但是大多時候並不是這樣,只能說可能存儲地址有一定關聯。
因此有人會說,可以直接根據hashcode值判斷兩個對象是否相等嗎?肯定是不可以的,因爲不同的對象可能會生成相同的hashcode值。雖然不能根據hashcode值判斷兩個對象是否相等,但是可以直接根據hashcode值判斷兩個對象不等,如果兩個對象的hashcode值不等,則必定是兩個不同的對象。如果要判斷兩個對象是否真正相等,必須通過equals方法。

也就是說對於兩個對象,如果調用equals方法得到的結果爲true,則兩個對象的hashcode值必定相等;
如果equals方法得到的結果爲false,則兩個對象的hashcode值不一定不同;
如果兩個對象的hashcode值不等,則equals方法得到的結果必定爲false;
如果兩個對象的hashcode值相等,則equals方法得到的結果未知。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章