深入淺出Java中的==,equals(), hashCode()
前言-----
==,equals(), hashCode()是不是傻傻分不清, 相信我, 看完這篇部落格就不會了
目錄
四.hashCode值的比較結果 和 equals()的比較結果在邏輯上應該一致
測試類
class Car{ String ID;//車牌號 String Brand;//品牌 String mileage //行駛的公里數 public Car(String ID,String Brand) { this.ID = ID; this.Brand = Brand; } }
一. 快速區分 ==和equals()的區別
- ==是判斷兩個物件的地址是否相同
- equals()方法如果沒有重寫的話,預設也是呼叫==方法進行比較
package Test; import java.util.HashMap; public class HashMapTest { public static void main(String[] args) { // TODO Auto-generated method stub Car car1 = new Car("1222","AA"); Car car2 = new Car("1222","AA"); System.out.println(car1 == car2); System.out.println(car1.equals(car2)); } }
測試結果:
由於car1和car2在在記憶體中的地址不相同,所以測試結果都是false
二.如何把ID和Brand相同的car判定為同一物件呢?
有時,我們需要根據物件的屬性值來判斷物件是否相同,而不是僅僅從物件的地址來判斷, 那麼如何做到呢?重寫equals()函式,
String 中用equals()比較字串內容否相同,本質上也是因為重寫了Object類中的equals()方法:
現在我們重寫Car類的equals()方法, 只要ID和Brand相同,就視為同一個物件:
package Test; import java.util.HashMap; public class HashMapTest { public static void main(String[] args) { // TODO Auto-generated method stub Car car1 = new Car("1222","AA"); Car car2 = new Car("1222","AA"); System.out.println(car1 == car2); System.out.println(car1.equals(car2)); } } class Car{ String ID;//車牌號 String Brand;//品牌 String mileage //行駛的公里數 public Car(String ID,String Brand) { this.ID = ID; this.Brand = Brand; } public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的 return (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand); } }
此時測試結果是false 和true, 說明equals()已經改寫咯
三.什麼是hashCode值
如果把物件類比為一個學生,那麼物件的地址相當於學生的身份證,是獨一無二的; 而hashCode值相當於一個學生的姓名,可能會有多個物件擁有相同的hashCode值, 如同姓名相同.
package Test;
import java.util.HashMap;
public class HashMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Car car1 = new Car("1222","AA");
Car car2 = new Car("1222","AA");
System.out.println(car1.hashCode());
System.out.println(car2.hashCode());
}
}
class Car{
String ID;//車牌號
String Brand;//品牌
String mileage //行駛的公里數
public Car(String ID,String Brand) {
this.ID = ID;
this.Brand = Brand;
}
public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的
return (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand);
}
}
測試結果:
car1的hashCode值為:2018699554
car2的hashCode值為: 1311053135
四.hashCode值的比較結果 和 equals()的比較結果在邏輯上應該一致
- 為什麼要一致?
還是以上面的car1和car2為例, 我們重寫了equals()的方法,car1.equals(car2)的結果true, 但是car1和car2的hashCode值卻不同,用hashCode值判是否相同的話,結果會是false, 二者的結果是不相同的. 這在邏輯上是說不過去的, 所以我們應該另hashCode值的比較結果和equals()的比較結果在邏輯上一致 ,這也是一個良好的習慣.
- 那麼如何另二者的比較結果一致呢?
重寫hashCode方法,且必須設計一種生成機制, 在這種機制下,ID 和Brand的Car物件可以擁有相同的hashCod值, 不妨設計如下:
public int hashCode() {
return ID.hashCode() + Brand.hashCode();
}
測試demo:
import java.util.HashMap;
public class HashMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
Car car1 = new Car("1222","AA");
Car car2 = new Car("1222","AA");
System.out.println("car1的hashCode值為:" +car1.hashCode());
System.out.println("car2的hashCode值為:" +car2.hashCode());
System.out.print("用equals()的比較結果是:");
System.out.println( car1.equals(car2) );
System.out.print("用hashCode值的比較結果是:" );
System.out.println( car1.hashCode()==car2.hashCode() );
}
}
class Car{
String ID;//車牌號
String Brand;//品牌
String mileage //行駛的公里數
public Car(String ID,String Brand) {
this.ID = ID;
this.Brand = Brand;
}
public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的
return (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand);
}
public int hashCode() {
return ID.hashCode() + Brand.hashCode();
}
}
測試結果:
car1的hashCode值為:1511489
car2的hashCode值為:1511489
用equals()的比較結果是:true
用hashCode值的比較結果是:true
五.equals()和hashCode()重寫時的注意事項
注:此段內容參考了該部落格,感謝
- 在程式執行期間,只要equals方法的比較操作用到的資訊沒有被修改,那麼對這同一個物件呼叫多次,hashCode方法必須始終如一地返回同一個整數。
- 如果兩個物件根據equals方法比較是相等的,那麼呼叫兩個物件的hashCode方法必須返回相同的整數結果。
- 如果兩個物件根據equals方法比較是不等的,則hashCode方法不一定得返回不同的整數。
第二條的內容就是上面我所解釋的.
第三條的內容也好理解, 如果equals()結果是不等, 但是hashCode值有小概率是相等的.
第一條的內容: 就是說hashCode() 裡面用於生成hashCode的欄位最好是不容易改變的 ,以上面重寫的hashCode()方法為例.
public int hashCode() {
return ID.hashCode() + Brand.hashCode();
}
這邊hashCode值依靠於Car的ID和Brand這兩個屬性, 如果某個時候, ID或者Brand改變了,那麼對於的hashCode值也會改變了,
這又有什麼影響呢,不妨看段程式碼:
package Test;
import java.util.HashMap;
public class HashMapTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
HashMap <Car, String> myMap = new HashMap <Car,String>();
Car car1 = new Car("1222","AA");
System.out.print("此時的hashCode值為:");
System.out.println(car1.hashCode());
myMap.put(car1, "Jane");
car1.ID = "1111";
System.out.print("修改ID後,hashCode值為:");
System.out.println(car1.hashCode());
//判斷是否存在key為car1的Entry
System.out.println(myMap.get(car1));
}
}
class Car{
String ID;//車牌號
String Brand;//品牌
String mileage //行駛的公里數
public Car(String ID,String Brand) {
this.ID = ID;
this.Brand = Brand;
}
public boolean equals(Object obj) { //定義ID和Brand都相同的物件就是相同的
return (ID ==((Car)obj).ID ) && (Brand ==((Car)obj).Brand);
}
public int hashCode() {
return ID.hashCode() + Brand.hashCode();
}
}
測試結果:
此時的hashCode值為:1511489
修改ID後,hashCode值為:1510496
null
熟悉HashMap的朋友應該很清楚, HashMap中的get()方法是通過比較hash值來查詢的,而hash值又依賴於hashCode值,因此就不難理解為什麼結果是null了. 因為HashMap中car1的hashCode值和 修改ID後的car1的hashCode值不同了
為了儘量避免這種情況, 用於生成hashCode()的欄位在使用過程中應該是比較不會頻繁更改的欄位, 所以這也是為什麼不用 mileage (行駛的公里數)來生成hashCode值的原因了, 它更容易被更改.