在實際應用中經常會比較兩個對象是否相等,比如下面的Address類,它有兩個屬性:String province 和 String city。
public class Address { private String province; private String city; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public Address() {} public Address(String province, String city) {this.province = province; this.city = city;} }
在現實生活中我們認為若兩個 Address 的 province 和 city 屬性相同,則它們應該是同一個地址(省市都一樣,當然就是同一個地區啦)。但下面的代碼卻表明:address1 和 address2 是兩個“不同的地址”
1 public class TestAddress { 2 3 public static void main(String[] args) { 4 Address address1 = new Address("廣東","廣州"); 5 Address address2 = new Address("廣東", "廣州"); 6 7 System.out.println(address1 == address2);//false 8 System.out.println(address1.equals(address2));//false 9 System.out.println(address1.hashCode() == address2.hashCode());//false 10 } 11 }
其原因是:在JAVA(編程語言)中比較對象 和 現實生活中 比較兩個對象是兩個不同的概念,前者暫且稱為“物理相等”,而后者是“邏輯相等”。
adress1==adress2 是根據兩個對象的內存地址是否相同進行比較的,第4、5行分別 new 了兩個對象,這兩個對象存儲在內存中不同的地址,當然不會相等了。
由於Address類並沒有重寫equals方法,那么address1.equals(address2) 執行的就是Object類的equals方法,而java.lang.Object類的equlas方法是這樣實現的:
public boolean equals(Object obj) { return (this == obj); }
JDK中對該方法的注釋如下:也就是說:Object類的equals方法 是通過 == 來比較兩個對象是否相等的,也即根據 對象x引用 和 對象y 的引用是否指向內存中的同一個地址 來判斷 對象x 和 對象y 是否相等。
* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only * if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
而按照現實思維,既然 adress1 和 address2 都代表廣東廣州,那么在程序中它們兩個對象比較就應該相等(邏輯上相等),也即address1.equals(address2)應該返回true才對。於是就需要覆蓋 Object 類中的 equals 方法 和 hashCode方法了。
而覆蓋 equals方法 和hashCode方法是需要技巧的。
①覆蓋了Object類的equals方法后,需要再覆蓋 Object類的hashCode方法。為什么呢?----不要違反“hashCode方法的 通過約定(general contract) ”
Object類的equals方法上的注釋如下:
/ * Note that it is generally necessary to override the {@code hashCode} * method whenever this method is overridden, so as to maintain the * general contract for the {@code hashCode} method, which states * that equal objects must have equal hash codes. */ public boolean equals(Object obj) { return (this == obj); }
而這個“通用約定”就是:❶若兩個對象根據equals(Object)方法比較相等,那么調用這兩個對象中任意一個對象的hashCode方法 都必須 產生同樣的整數結果。
❷若兩個對象根據equals(Object)方法比較不相等,那么調用這兩個對象中任意一個對象的hashCode方法 可以 產生相同的整數結果,但是最好 產生不同的整數結果,這樣可以提供散列表的性能(當要把Address類 作為 鍵 put 到HashMap中時,可以減少沖突,可參考這篇文章)
那具體如何正確地覆蓋equals()呢?《Effective JAVA》里面給出了方法,套路是一樣的,其目標是保證:自反性、對稱性、一致性。總之,對於上面的Address類而言,可以這樣:
1 @Override 2 public boolean equals(Object obj) { 3 if(obj == this) 4 return true; 5 if(!(obj instanceof Address)) 6 return false; 7 Address address = (Address)obj; 8 return address.getProvince().equals(province) && address.getCity().equals(city); 9 }
第8行從表明如果兩個Address的 province 和 city 相同,那這兩個Address就是相同的,這樣equals方法就會返回true了。(不考慮字符串大小寫問題)
覆蓋完了equals(),接下來就是 覆蓋hashCode()了。覆蓋hashCode()的目標是:
如果兩個對象 address1.equals(address2) 返回 false,那么 address1.hashCode() 最好 不等於 address2.hashCode()
當然,沒有超級完美的hashCode(),如果相等了,那么當 hashMap.put(address1,value1) hashMap.put(address2,value2) 就會put到同一個 hashmap的同一個槽下了。
重寫了equals和hashCode的Address類如下:
public class Address { private String province; private String city; public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public Address() {} public Address(String province, String city) {this.province = province; this.city = city;} @Override public boolean equals(Object obj) { if(obj == this) return true; if(!(obj instanceof Address)) return false; Address address = (Address)obj; return address.getProvince().equals(province) && address.getCity().equals(city); } @Override public int hashCode() { int result = 17; result += 31 * province.hashCode(); result += 31 * city.hashCode(); return result; } }
測試類如下:
import java.util.HashMap; import java.util.Map; public class TestAddress { public static void main(String[] args) { Address address1 = new Address("廣東","廣州"); Address address2 = new Address("廣東", "廣州"); System.out.println(address1 == address2);//false System.out.println(address1.equals(address2));//true System.out.println(address1.hashCode() == address2.hashCode());//true Address diff1 = new Address("四川","成都"); Address diff2 = new Address("四川","綿陽"); System.out.println(diff1 == diff2);//false System.out.println(diff1.equals(diff2));//false System.out.println(diff1.hashCode() == diff2.hashCode());//false Map<Address, Integer> hashMap = new HashMap<Address, Integer>(); hashMap.put(address1, 1); hashMap.put(address2, 2);//address2的hashCode 和 address1 相同,因此 put 方法會覆蓋 address1 對應的 Value值1 System.out.println(hashMap.get(address1));//2 System.out.println(hashMap.get(address2));//2 hashMap.put(diff1, 1); hashMap.put(diff2, 2); System.out.println(hashMap.get(diff1));//1 System.out.println(hashMap.get(diff2));//2 } }
最后,其實Eclipse里面為我們提供了自動 生成 equals和hashCode的方法,可參考:JAVA中equals方法與hashCode方法學習。自動生成的方法如下:(還是自動生成的更專業呀。。。。)
@Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((city == null) ? 0 : city.hashCode()); result = prime * result + ((province == null) ? 0 : province.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Address other = (Address) obj; if (city == null) { if (other.city != null) return false; } else if (!city.equals(other.city)) return false; if (province == null) { if (other.province != null) return false; } else if (!province.equals(other.province)) return false; return true; }
參考:《effective java》
http://www.cnblogs.com/hapjin/p/4582795.html
原文:http://www.cnblogs.com/hapjin/p/7327839.html