天天看點

為什麼重寫equals一定要重寫hashcode?               為什麼重寫equals一定要重寫hashcode?

               為什麼重寫equals一定要重寫hashcode?

我們都知道,要比較兩個對象是否相等時需要調用對象的equals()方法,即判斷對象引用所指向的對象位址是否相等,對象位址相等時,那麼與對象相關的對象句柄、對象頭、對象執行個體資料、對象類型資料等也是完全一緻的,是以我們可以通過比較對象的位址來判斷是否相等。

對象在不重寫的情況下使用的是Object的equals方法和hashcode方法,從Object類的源碼我們知道,預設的equals 判斷的是兩個對象的引用指向的是不是同一個對象;而hashcode也是根據對象位址生成一個整數數值;

另外我們可以看到Object的hashcode()方法的修飾符為native,表明該方法是否作業系統實作,java調用作業系統底層代碼擷取哈希值。

需要重寫equals()的場景

假設現在有很多學生對象,預設情況下,要判斷多個學生對象是否相等,需要根據位址判斷,若對象位址相等,那麼對象的執行個體資料一定是一樣的,但現在我們規定:當學生的姓名、年齡、性别相等時,認為學生對象是相等的,不一定需要對象位址完全相同,例如學生A對象所在位址為100,學生A的個人資訊為(姓名:A,性别:女,年齡:18,住址:北京軟體路999号,體重:48),學生A對象所在位址為388,學生A的個人資訊為(姓名:A,性别:女,年齡:18,住址:廣州暴富路888号,體重:55),這時候如果不重寫Object的equals方法,那麼傳回的一定是false不相等,這個時候就需要我們根據自己的需求重寫equals()方法了。

package jianlejun.study;
 
public class Student {
	private String name;// 姓名
	private String sex;// 性别
	private String age;// 年齡
	private float weight;// 體重
	private String addr;// 位址
	
	// 重寫hashcode方法
	@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}
 
	// 重寫equals方法
	@Override
	public boolean equals(Object obj) {
		if(!(obj instanceof Student)) {
       // instanceof 已經處理了obj = null的情況
			return false;
		}
		Student stuObj = (Student) obj;
		// 位址相等
		if (this == stuObj) {
			return true;
		}
		// 如果兩個對象姓名、年齡、性别相等,我們認為兩個對象相等
		if (stuObj.name.equals(this.name) && stuObj.sex.equals(this.sex) && stuObj.age.equals(this.age)) {
			return true;
		} else {
			return false;
		}
	}
 
	public String getName() {
		return name;
	}
 
	public void setName(String name) {
		this.name = name;
	}
 
	public String getSex() {
		return sex;
	}
 
	public void setSex(String sex) {
		this.sex = sex;
	}
 
	public String getAge() {
		return age;
	}
 
	public void setAge(String age) {
		this.age = age;
	}
 
	public float getWeight() {
		return weight;
	}
 
	public void setWeight(float weight) {
		this.weight = weight;
	}
 
	public String getAddr() {
		return addr;
	}
 
	public void setAddr(String addr) {
		this.addr = addr;
	}
 
}
           

現在我們寫個例子測試下結果: 

public static void main(String[] args) {
	Student s1 =new Student();
	s1.setAddr("1111");
	s1.setAge("20");
	s1.setName("allan");
	s1.setSex("male");
	s1.setWeight(60f);
	Student s2 =new Student();
	s2.setAddr("222");
	s2.setAge("20");
	s2.setName("allan");
	s2.setSex("male");
	s2.setWeight(70f);
	if(s1.equals(s2)) {
		System.out.println("s1==s2");
	}else {
		System.out.println("s1 != s2");
	}
}
           

在重寫了student的equals方法後,這裡會輸出s1 == s2,實作了我們的需求,如果沒有重寫equals方法,那麼上段代碼必定輸出s1!=s2。 

大意就是說 equals()是比較的内容,但是底層比較的是位址,你想用就要重寫。

以上面例子為基礎,即student1和student2在重寫equals方法後被認為是相等的。需要重寫hashcode()的場景

需要重寫hashcode()的場景

在兩個對象equals的情況下進行把他們分别放入Map和Set中

Set set = new HashSet();
	set.add(s1);
	set.add(s2);
	System.out.println(set);
           

如果沒有重寫Object的hashcode()方法(即去掉上面student類中hashcode方法塊),這裡會輸出

[jian[email protected], [email protected]]
           

說明該Set容器類有2個元素。.........等等,為什麼會有2個元素????剛才經過測試,s1不是已經等于s2了嗎,那按照Set容器的特性會有一個去重操作,那為什麼現在會有2個元素。這就涉及到Set的底層實作問題了,這裡簡單介紹下就是HashSet的底層是通過HashMap實作的,最終比較set容器内元素是否相等是通過比較對象的hashcode來判斷的。現在你可以試試吧剛才注釋掉的hashcode方法弄回去,然後重新運作,看是不是很神奇的就隻輸出一個元素了

@Override
	public int hashCode() {
		int result = name.hashCode();
		result = 17 * result + sex.hashCode();
		result = 17 * result + age.hashCode();
		return result;
	}
           

或許你會有一個疑問?hashcode裡的代碼該怎麼了解?該如何寫?其實有個相對固定的寫法,先整理出你判斷對象相等的屬性,然後取一個盡可能小的正整數(盡可能小時怕最終得到的結果超出了整型int的取數範圍),這裡我取了17,(好像在JDK源碼中哪裡看過用的是17),然後計算17*屬性的hashcode+其他屬性的hashcode,重複步驟。

————————————————

重寫hashcode方法後輸出的結果為:

[[email protected]]

同理,可以測試下放入HashMap中,key為<s1,s1>,<s2,s2>,Map也把兩個同樣的對象當成了不同的Key(Map的Key是不允許重複的,相同Key會覆寫)那麼沒有重寫的情況下map中也會有2個元素,重寫的情況會最後put進的元素會覆寫前面的value

Map m = new HashMap();
	m.put(s1, s1);
	m.put(s2, s2);
	System.out.println(m);
	System.out.println(((Student)m.get(s1)).getAddr());
 
輸出結果:
{jianlej[email protected][email protected]}
222
           

 可以看到最終輸出的位址資訊為222,222是s2成員變量addr的值,很明天,s2已經替換了map中key為s1的value值,最終的結果是map<s1,s2>。即key為s1value為s2.

原理分析

因為我們沒有重寫父類(Object)的hashcode方法,Object的hashcode方法會根據兩個對象的位址生成對相應的hashcode;

s1和s2是分别new出來的,那麼他們的位址肯定是不一樣的,自然hashcode值也會不一樣。

Set差別對象是不是唯一的标準是,兩個對象hashcode是不是一樣,再判定兩個對象是否equals;

Map 是先根據Key值的hashcode配置設定和擷取對象儲存數組下标的,然後再根據equals區分唯一值(詳見下面的map分析)

hashcode方法文檔說明

在equals方法沒被修改的前提下,多次調用同一對象的hashcode方法傳回的值必須是相同的整數;

如果兩個對象互相equals,那麼這兩個對象的hashcode值必須相等;

為不同對象生成不同的hashcode可以提升哈希表的性能;

參考連接配接

https://blog.csdn.net/u012557538/article/details/89861552?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://blog.csdn.net/javazejian/article/details/51348320?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

https://blog.csdn.net/xl_1803/article/details/80445481?depth_1-utm_source=distribute.pc_relevant.none-task&utm_source=distribute.pc_relevant.none-task

為什麼重寫equals一定要重寫hashcode?               為什麼重寫equals一定要重寫hashcode?