一、public boolean equals(Object obj) 和 hashcode()方法是object對象中的方法。
二、equals與hashcode間的關系是這樣的:
1、如果兩個對象相同(即用equals比較返回true),那么它們的hashCode值一定要相同;
2、如果兩個對象的hashCode相同,它們并不一定相同(即用equals比較返回false)。
即:
1、當obj1.equals(obj2)為true時,obj1.hashCode() == obj2.hashCode() 必須為true;
2、當obj1.hashCode() == obj2.hashCode()為false時,obj1.equals(obj2) 必須為false。
三、為啥重寫equals?
如果不重寫equals,那么比較的將是對象的引用是否指向同一塊內存地址,重寫之后目的是為了比較兩個對象的value值是否相等。特別指出利用equals比較八大包裝對象(如int,float等)和String類(因為該類已重寫了equals和hashcode方法)對象時,默認比較的是值,在比較其它自定義對象時都是比較的引用地址。
四、什么是hashcode?
hashcode是用于散列數據的快速存取,如利用HashSet/HashMap/Hashtable類來存儲數據時,都是根據存儲對象的hashcode值來進行判斷是否相同的。由于為了提高程序的效率才實現了hashcode方法,先進行hashcode的比較,如果不同,那沒就不必在進行equals的比較了,這樣就大大減少了equals比較的次數。一個很好的例子就是在集合中的使用,我們都知道java中的List集合是有序的,因此是可以重復的,而set集合是無序的,因此是不能重復的,那么怎么能保證不能被放入重復的元素呢,單靠equals方法一樣比較的話,如果原來集合中以后又10000個元素了,那么放入10001個元素,難道要將前面的所有元素都進行比較,看看是否有重復,這個效率可想而知,因此hashcode就應遇而生了,java就采用了hash表,利用哈希算法(也叫散列算法),就是將對象數據根據該對象的特征使用特定的算法將其定義到一個地址上,那么在后面定義進來的數據只要看對應的hashcode地址上是否有值,如果有那么就用equals比較,如果沒有則直接插入,這樣就大大減少了equals的使用次數,執行效率就大大提高了。繼續上面的話題,為什么必須要重寫hashcode方法,其實簡單的說就是為了保證同一個對象,保證在equals相同的情況下hashcode值必定相同,如果重寫了equals而未重寫hashcode方法,可能就會出現兩個沒有關系的對象equals相同的(因為equals都是根據對象的特征進行重寫的),但hashcode確實不相同的。
五、那么如何保證這一點?
這樣如果我們對一個對象重寫了euqals,意思是只要對象的成員變量值相等那么euqals就等于true,但不重寫hashcode,那么我們再new一個新的對象,當原對象.equals(新對象)等于true時,兩者的hashcode卻是不一樣的,由此將產生了理解的不一致,如在存儲散列集合時(如Set類),將會存儲了兩個值一樣的對象,導致混淆,因此,就也需要重寫hashcode()。
在集合框架中的HashSet,HashTable和HashMap都使用哈希表的形式存儲數據,而hashCode計算出來的哈希碼便是它們的身份證。哈希碼的存在便可以:
1、快速定位對象,提高哈希表集合的性能。
2、只有當哈希表中對象的索引即hashCode和對象的屬性即equals同時相等時,才能夠判斷兩個對象相等。
3、從上面可以看出,哈希碼主要是為哈希表服務的,其實如果不需要使用哈希表,也可以不重寫hashCode。但是SUN公司應該是出于對程序擴展性的考慮(萬一以后需要將對象放入哈希表集合中),才會規定重寫equals的同時需要重寫hashCode,以避免后續開發不必要的麻煩。
重寫equals的重要性
Java語言規范要求equals需要具有如下的特性:
1.自反性:對于任何非空引用 x,x.equals() 應該返回 true。
2.對稱性:對于任何引用 x 和 y,當且僅當 y.equals(x) 返回 true,x.equals(y) 也應該返回 true。
3.傳遞性:對于任何引用 x、y 和 z,如果 x.equals(y)返回 true,y.equals(z) 也應返回同樣的結果。
4.一致性:如果 x 和 y 引用的對象沒有發生變化,反復調用 x.equals(y) 應該返回同樣的結果。
5.對于任意非空引用 x,x.equals(null) 應該返回 false。
在對象比較時,我們應該如何編寫出一個符合特性的 equals 方法呢,《Core Java》中提出了如下建議:
1、顯式參數命名為 otherObject,稍后將它轉換成另一個叫做 other 的變量。
2、檢測 this 與 otherObject 是否引用同一個對象:
if (this == otherObject) return true;
3、檢測 otherObject 是否為 null,如果為 null,返回 false。進行非空校驗是十分重要的。
4、比較 this 與 otherObject 是否屬于同一個類。
- 如果每個子類都重寫了
equals
,使用getClass
檢驗:
if (getClass() != otherObject.getClass()) return false;
- 如果所有子類都使用同一個
equals
,就用instanceof
檢驗:
if (!(otherObject instanceof ClassName)) return false;
5、將 otherObject 轉換為相應的類型變量。
ClassName other = (ClassName) otherObject;
6、現在可以對所有需要比較的域進行比較了。
- 基本類型使用
==
比較 - 對象使用
equals
比較 - 數組類型的域可以使用靜態方法
Arrays.equals
檢測相應數組元素是否相等 - 如果所有域匹配,則返回 true
注意:子類重寫父類 equals
方法時,必須完全覆蓋父類方法,不能因為類型錯誤或者其他原因定義了一個完全無關的方法。可以使用 @Override
注解對覆蓋父類的方法進行標記,這樣編譯器便會檢測到覆蓋過程中的錯誤。
重寫 hashcode 的注意事項
散列碼(hash code)是由對象導出的一個整型值。散列碼沒有規律,在不同的對象中通過不同的算法生成,Java中生成 hashCode 的策略為(以下說明均摘自 Java API 8):
String 類的 hashCode 根據其字符串內容,使用算法計算后返回哈希碼。
Returns a hash code for this string. The hash code for a String object is computed as s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
Integer 類返回的哈希碼為其包含的整數數值。
Returns: a hash code value for this object, equal to the primitive int value represented by this Integer object.
Object 類的 hashCode 返回對象的內存地址經過處理后的數值。
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap.
在自己的類中想要重寫 hashCode
的話一般怎么做呢?建議合理地組合實例域的散列碼,讓各個不同對象產生的散列碼更加均勻。例如我們現在有一個 Cat
對象,它有 name
、size
和 color
三個不同域,那么可以重寫 hashCode
方法如下:
class Cat { ...... public int hashCode() { //hashCode是可以返回負值的 return 6 * name.hashCode() + 8 * new Double(size).hashCode() + 10 * color.hashCode(); } ...... }
當然還有更好的做法,我們可以直接調用靜態方法 Objects.hash
并提供多個參數。這個方法會對各個參數調用 Object.hashCode
,并組合返回的散列碼。故以上的方法可以縮寫為:
public int hashCode() { return Objects.hash(name, size, color); }
注意: equals
與hashCode
的定義必須一致,兩個對象equals
為true
,就必須有相同的hashCode
。例如:如果定義的equals
比較的是小貓的 name,那么hashCode
就需要散列該 name,而不是小貓的 color 或 size。
文章列表