記憶體泄漏避雷!你真的了解重寫equals()和hashcode()方法的原因嗎?
2021 年 12 月 15 日
筆記
java技術
基本概念
要比較兩個對象是否相等時需要調用對象的equals() 方法:
對象地址相等時, 那麼對象相關的數據也相等,包括:
可以通過比較對象的地址來判斷對象是否相等
Object源碼
對象在不重寫的情況下使用的是Object 中的equals() 方法和hashCode() 方法
equals(): 判斷的是兩個對象的引用是否指向同一個對象
hashCode(): 根據對象地址生成一個整數數值
Object 的hashCode() 方法修飾符為native: 表明該方法是由作業系統實現. Java調用作業系統底層程式碼獲取Hash 值
public native int hashCode();
重寫equals
重寫equals()方法的場景:
假設現在有很多學生對象
默認情況下,要判斷多個學生對象是否相等,需要根據地址判斷:
判斷相等的要求:
當學生的姓名,年齡,性別相等時,認為對象是相等的,
不一定需要對象的地址完全相同
根據需求重寫equals()方法:
public class Student {
/** 姓名 */
private String name;
/** 性別 */
private String sex;
/** 年齡 */
private String age;
/** 體重 */
private float weight;
/** 地址 */
private String addr;
/*
* 重寫equals()方法
*/
@Override
public boolean equals(Object obj) {
// instanceof已經處理了obj == null的情況
if (! (Object instanceof Student)) {
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 String getWeight() {
return weight;
}
public void setName(String 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("earth");
s1.setAge("20");
s1.setName("Tom");
s1.setSex("Male");
s1.setWeight(60f);
Student s2 = new Student();
s2.setAddr("Mars");
s2.setAge("20");
s2.setName("Tom");
s2.setSex("Male");
s2.setWeight(70f);
if (s1.equals(s2)) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
}
重寫了equals() 方法後,這裡會輸出 [s1==s2]
如果沒有重寫 equals() 方法,那麼必定會輸出 [s1!=s2]
重寫hashCode
根據重寫equals的方法,上述s1和s2認為是相等的
Object中的hashCode()方法:
在equals() 方法沒被修改的前提下,多次調用同一個對象的hashCode() 方法返回的值必須是相同的正數
如果兩個對象互相equals(), 那麼這兩個對象的hashcode 值必須相等
為不同的對象生成不同的hashcode 可以提升Hash 表的性能
重寫hashCode()方法:
```java
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) {
// instanceof已經處理了obj == null的情況
if (! (Object instanceof Student)) {
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 String getWeight() {
return weight;
}
public void setName(String weight) {
this.weight = weight;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
}
在兩個對象相等的情況下,分別放入Map和Set中:
public static void main(String[] args) {
Student s1 = new Student();
s1.setAddr("earth");
s1.setAge("20");
s1.setName("Tom");
s1.setSex("Male");
s1.setWeight(60f);
Student s2 = new Student();
s2.setAddr("Mars");
s2.setAge("20");
s2.setName("Tom");
s2.setSex("Male");
s2.setWeight(70f);
if (s1.equals(s2)) {
System.out.println("s1 == s2");
} else {
System.out.println("s1 != s2");
}
Set set = new HashSet();
set.add(s1);
set.add(s2);
System.out.println(Set);
}
如果沒有重寫Object 的hashCode() 方法,會出現:
[com.oxford.Student@7852e922, com.oxford.Student@4e25154f]
這是不符合預期的,因為Set容器有去重的特性.相等的元素不會重複顯示.這就涉及到Set的底層實現了
HashSet底層實現:
HashSet 底層是通過HashMap 實現的
比較Set 容器內元素是否相等是通過比較對象的hashcode 來判斷是否相等的
hashCode()的寫法:
首先整理出判斷對象相等的屬性
然後去一個儘可能小的正整數,防止最終結果超出整型int 的取數範圍
然後計算[正整數 * 屬性的hashCode + 其餘某個屬性的hashCode]
重複步驟
/*
* 重寫hashCode()方法
*/
@Override
public int hashCode() {
int result = name.hashCode();
result = 17 * result + sex.hashCode();
result = 17 * result + age.hashCode();
return result;
}
原理分析
因為沒有重寫父類的Object 的hashCode() 方法,所以Object 的hashCode() 方法會根據兩個對象的地址生成響應的hashcode
由於兩個對象分別是實體類創建的不同的實例,所以地址肯定是不一樣的,那麼hashcode 值也是不一樣的
Set區別對象是不是唯一的標準:
兩個對象的hashcode 值是否一樣
然後再判定兩個對象是否equals
Map區別對象是不是唯一的標準:
先根據Key 值的hashcode 分配來獲取保存數組下標
然後再根據eaquals 區分是否是唯一值
HashMap
HashMap組成結構
HashMap的存儲
HashMap的存儲:
一個對象存儲到HashMap 中的位置是由key 的hashcode 值決定的
HashMap查找key:
查找key 時 ,hashMap 會先根據key 值的hashcode 經過取余演算法定位所在數組的位置
然後根據key 的equals 方法匹配相同的key 值獲取相應的對象
存值規則:
將Key 的hashcode 與HashMap 的容量,進行取余 運算得出該Key 存儲在數組所在位置的下標
HashMap查找key:
得到key 在數組中的位置
匹配得到對應key 值對象
然後將上述多個對象根據key.equals() 來匹配獲取對應的key的數據對象
HashMap中的hashCode:
如果沒有hashcode 就意味著HashMap 存儲的時候是沒有規律可循的
這樣每次使用map.get() 方法,就要將map里的對象一一進行equals 匹配,導致效率低下