一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

重寫equal()時(shí)為什么也得重寫hashCode()之深度解讀equal方法與hashCode方法淵源

 vnxy001 2019-02-26

轉(zhuǎn)載請(qǐng)注明出處微笑

http://blog.csdn.net/javazejian/article/details/51348320

今天這篇文章我們打算來深度解讀一下equal方法以及其關(guān)聯(lián)方法hashCode(),我們準(zhǔn)備從以下幾點(diǎn)入手分析:


1.equals()的所屬以及內(nèi)部原理(即Object中equals方法的實(shí)現(xiàn)原理)

說起equals方法,我們都知道是超類Object中的一個(gè)基本方法,用于檢測(cè)一個(gè)對(duì)象是否與另外一個(gè)對(duì)象相等。而在Object類中這個(gè)方法實(shí)際上是判斷兩個(gè)對(duì)象是否具有相同的引用,如果有,它們就一定相等。其源碼如下:

public boolean equals(Object obj) {   return (this == obj);     }

實(shí)際上我們知道所有的對(duì)象都擁有標(biāo)識(shí)(內(nèi)存地址)和狀態(tài)(數(shù)據(jù)),同時(shí)“==”比較兩個(gè)對(duì)象的的內(nèi)存地址,所以說 Object 的 equals() 方法是比較兩個(gè)對(duì)象的內(nèi)存地址是否相等,即若 object1.equals(object2) 為 true,則表示 equals1 和 equals2 實(shí)際上是引用同一個(gè)對(duì)象。

2.equals()與‘==’的區(qū)別

或許這是我們面試時(shí)更容易碰到的問題”equals方法與‘==’運(yùn)算符有什么區(qū)別?“,并且常常我們都會(huì)胸有成竹地回答:“equals比較的是對(duì)象的內(nèi)容,而‘==’比較的是對(duì)象的地址。”。但是從前面我們可以知道equals方法在Object中的實(shí)現(xiàn)也是間接使用了‘==’運(yùn)算符進(jìn)行比較的,所以從嚴(yán)格意義上來說,我們前面的回答并不完全正確。我們先來看一段代碼并運(yùn)行再來討論這個(gè)問題。

  1. package com.zejian.test;
  2. public class Car {
  3. private int batch;
  4. public Car(int batch) {
  5. this.batch = batch;
  6. }
  7. public static void main(String[] args) {
  8. Car c1 = new Car(1);
  9. Car c2 = new Car(1);
  10. System.out.println(c1.equals(c2));
  11. System.out.println(c1 == c2);
  12. }
  13. }

運(yùn)行結(jié)果:

false

false

分析:對(duì)于‘==’運(yùn)算符比較兩個(gè)Car對(duì)象,返回了false,這點(diǎn)我們很容易明白,畢竟它們比較的是內(nèi)存地址,而c1與c2是兩個(gè)不同的對(duì)象,所以c1與c2的內(nèi)存地址自然也不一樣?,F(xiàn)在的問題是,我們希望生產(chǎn)的兩輛的批次(batch)相同的情況下就認(rèn)為這兩輛車相等,但是運(yùn)行的結(jié)果是盡管c1與c2的批次相同,但equals的結(jié)果卻反回了false。當(dāng)然對(duì)于equals返回了false,我們也是心知肚明的,因?yàn)閑qual來自O(shè)bject超類,訪問修飾符為public,而我們并沒有重寫equal方法,故調(diào)用的必然是Object超類的原始方equals方法,根據(jù)前面分析我們也知道該原始equal方法內(nèi)部實(shí)現(xiàn)使用的是'=='運(yùn)算符,所以返回了false。因此為了達(dá)到我們的期望值,我們必須重寫Car的equal方法,讓其比較的是對(duì)象的批次(即對(duì)象的內(nèi)容),而不是比較內(nèi)存地址,于是修改如下:

  1. @Override
  2. public boolean equals(Object obj) {
  3. if (obj instanceof Car) {
  4. Car c = (Car) obj;
  5. return batch == c.batch;
  6. }
  7. return false;
  8. }

使用instanceof來判斷引用obj所指向的對(duì)象的類型,如果obj是Car類對(duì)象,就可以將其強(qiáng)制轉(zhuǎn)為Car對(duì)象,然后比較兩輛Car的批次,相等返回true,否則返回false。當(dāng)然如果obj不是 Car對(duì)象,自然也得返回false。我們?cè)俅芜\(yùn)行:

true

false

嗯,達(dá)到我們預(yù)期的結(jié)果了。因?yàn)榍懊娴拿嬖囶}我們應(yīng)該這樣回答更佳
總結(jié):默認(rèn)情況下也就是從超類Object繼承而來的equals方法與‘==’是完全等價(jià)的,比較的都是對(duì)象的內(nèi)存地址,但我們可以重寫equals方法,使其按照我們的需求的方式進(jìn)行比較,如String類重寫了equals方法,使其比較的是字符的序列,而不再是內(nèi)存地址。

3.equals()的重寫規(guī)則

前面我們已經(jīng)知道如何去重寫equals方法來實(shí)現(xiàn)我們自己的需求了,但是我們?cè)谥貙慹quals方法時(shí),還是需要注意如下幾點(diǎn)規(guī)則的。

  • 自反性。對(duì)于任何非null的引用值x,x.equals(x)應(yīng)返回true。

  • 對(duì)稱性。對(duì)于任何非null的引用值x與y,當(dāng)且僅當(dāng):y.equals(x)返回true時(shí),x.equals(y)才返回true。

  • 傳遞性。對(duì)于任何非null的引用值x、y與z,如果y.equals(x)返回true,y.equals(z)返回true,那么x.equals(z)也應(yīng)返回true。

  • 一致性。對(duì)于任何非null的引用值x與y,假設(shè)對(duì)象上equals比較中的信息沒有被修改,則多次調(diào)用x.equals(y)始終返回true或者始終返回false。

  • 對(duì)于任何非空引用值x,x.equal(null)應(yīng)返回false。

當(dāng)然在通常情況下,如果只是進(jìn)行同一個(gè)類兩個(gè)對(duì)象的相等比較,一般都可以滿足以上5點(diǎn)要求,下面我們來看前面寫的一個(gè)例子。

  1. package com.zejian.test;
  2. public class Car {
  3. private int batch;
  4. public Car(int batch) {
  5. this.batch = batch;
  6. }
  7. public static void main(String[] args) {
  8. Car c1 = new Car(1);
  9. Car c2 = new Car(1);
  10. Car c3 = new Car(1);
  11. System.out.println("自反性->c1.equals(c1):" + c1.equals(c1));
  12. System.out.println("對(duì)稱性:");
  13. System.out.println(c1.equals(c2));
  14. System.out.println(c2.equals(c1));
  15. System.out.println("傳遞性:");
  16. System.out.println(c1.equals(c2));
  17. System.out.println(c2.equals(c3));
  18. System.out.println(c1.equals(c3));
  19. System.out.println("一致性:");
  20. for (int i = 0; i < 50; i++) {
  21. if (c1.equals(c2) != c1.equals(c2)) {
  22. System.out.println("equals方法沒有遵守一致性!");
  23. break;
  24. }
  25. }
  26. System.out.println("equals方法遵守一致性!");
  27. System.out.println("與null比較:");
  28. System.out.println(c1.equals(null));
  29. }
  30. @Override
  31. public boolean equals(Object obj) {
  32. if (obj instanceof Car) {
  33. Car c = (Car) obj;
  34. return batch == c.batch;
  35. }
  36. return false;
  37. }
  38. }

運(yùn)行結(jié)果:

自反性->c1.equals(c1):true

對(duì)稱性:

true

true

傳遞性:

true

true

true

一致性:

equals方法遵守一致性!

與null比較:

false

由運(yùn)行結(jié)果我們可以看出equals方法在同一個(gè)類的兩個(gè)對(duì)象間的比較還是相當(dāng)容易理解的。但是如果是子類與父類混合比較,那么情況就不太簡(jiǎn)單了。下面我們來看看另一個(gè)例子,首先,我們先創(chuàng)建一個(gè)新類BigCar,繼承于Car,然后進(jìn)行子類與父類間的比較。

  1. package com.zejian.test;
  2. public class BigCar extends Car {
  3. int count;
  4. public BigCar(int batch, int count) {
  5. super(batch);
  6. this.count = count;
  7. }
  8. @Override
  9. public boolean equals(Object obj) {
  10. if (obj instanceof BigCar) {
  11. BigCar bc = (BigCar) obj;
  12. return super.equals(bc) && count == bc.count;
  13. }
  14. return false;
  15. }
  16. public static void main(String[] args) {
  17. Car c = new Car(1);
  18. BigCar bc = new BigCar(1, 20);
  19. System.out.println(c.equals(bc));
  20. System.out.println(bc.equals(c));
  21. }
  22. }

運(yùn)行結(jié)果:

true

false

對(duì)于這樣的結(jié)果,自然是我們意料之中的啦。因?yàn)锽igCar類型肯定是屬于Car類型,所以c.equals(bc)肯定為true,對(duì)于bc.equals(c)返回false,是因?yàn)镃ar類型并不一定是BigCar類型(Car類還可以有其他子類)。嗯,確實(shí)是這樣。但如果有這樣一個(gè)需求,只要BigCar和Car的生產(chǎn)批次一樣,我們就認(rèn)為它們兩個(gè)是相當(dāng)?shù)?,在這樣一種需求的情況下,父類(Car)與子類(BigCar)的混合比較就不符合equals方法對(duì)稱性特性了。很明顯一個(gè)返回true,一個(gè)返回了false,根據(jù)對(duì)稱性的特性,此時(shí)兩次比較都應(yīng)該返回true才對(duì)。那么該如何修改才能符合對(duì)稱性呢?其實(shí)造成不符合對(duì)稱性特性的原因很明顯,那就是因?yàn)镃ar類型并不一定是BigCar類型(Car類還可以有其他子類),在這樣的情況下(Car instanceof BigCar)永遠(yuǎn)返回false,因此,我們不應(yīng)該直接返回false,而應(yīng)該繼續(xù)使用父類的equals方法進(jìn)行比較才行(因?yàn)槲覀兊男枨笫桥蜗嗤?,兩個(gè)對(duì)象就相等,父類equals方法比較的就是batch是否相同)。因此BigCar的equals方法應(yīng)該做如下修改:

  1. @Override
  2. public boolean equals(Object obj) {
  3. if (obj instanceof BigCar) {
  4. BigCar bc = (BigCar) obj;
  5. return super.equals(bc) && count == bc.count;
  6. }
  7. return super.equals(obj);
  8. }
這樣運(yùn)行的結(jié)果就都為true了。但是到這里問題并沒有結(jié)束,雖然符合了對(duì)稱性,卻還沒符合傳遞性,實(shí)例如下:
  1. package com.zejian.test;
  2. public class BigCar extends Car {
  3. int count;
  4. public BigCar(int batch, int count) {
  5. super(batch);
  6. this.count = count;
  7. }
  8. @Override
  9. public boolean equals(Object obj) {
  10. if (obj instanceof BigCar) {
  11. BigCar bc = (BigCar) obj;
  12. return super.equals(bc) && count == bc.count;
  13. }
  14. return super.equals(obj);
  15. }
  16. public static void main(String[] args) {
  17. Car c = new Car(1);
  18. BigCar bc = new BigCar(1, 20);
  19. BigCar bc2 = new BigCar(1, 22);
  20. System.out.println(bc.equals(c));
  21. System.out.println(c.equals(bc2));
  22. System.out.println(bc.equals(bc2));
  23. }
  24. }

運(yùn)行結(jié)果:

true

true

false

bc,bc2,c的批次都是相同的,按我們之前的需求應(yīng)該是相等,而且也應(yīng)該符合equals的傳遞性才對(duì)。但是事實(shí)上運(yùn)行結(jié)果卻不是這樣,違背了傳遞性。出現(xiàn)這種情況根本原因在于:

  • 父類與子類進(jìn)行混合比較。

  • 子類中聲明了新變量,并且在子類equals方法使用了新增的成員變量作為判斷對(duì)象是否相等的條件。

只要滿足上面兩個(gè)條件,equals方法的傳遞性便失效了。而且目前并沒有直接的方法可以解決這個(gè)問題。因此我們?cè)谥貙慹quals方法時(shí)這一點(diǎn)需要特別注意。雖然沒有直接的解決方法,但是間接的解決方案還說有滴,那就是通過組合的方式來代替繼承,還有一點(diǎn)要注意的是組合的方式并非真正意義上的解決問題(只是讓它們間的比較都返回了false,從而不違背傳遞性,然而并沒有實(shí)現(xiàn)我們上面batch相同對(duì)象就相等的需求),而是讓equals方法滿足各種特性的前提下,讓代碼看起來更加合情合理,代碼如下:

  1. package com.zejian.test;
  2. public class Combination4BigCar {
  3. private Car c;
  4. private int count;
  5. public Combination4BigCar(int batch, int count) {
  6. c = new Car(batch);
  7. this.count = count;
  8. }
  9. @Override
  10. public boolean equals(Object obj) {
  11. if (obj instanceof Combination4BigCar) {
  12. Combination4BigCar bc = (Combination4BigCar) obj;
  13. return c.equals(bc.c) && count == bc.count;
  14. }
  15. return false;
  16. }
  17. }

從代碼來看即使batch相同,Combination4BigCar類的對(duì)象與Car類的對(duì)象間的比較也永遠(yuǎn)都是false,但是這樣看起來也就合情合理了,畢竟Combination4BigCar也不是Car的子類,因此equals方法也就沒必要提供任何對(duì)Car的比較支持,同時(shí)也不會(huì)違背了equals方法的傳遞性。

4.equals()的重寫規(guī)則之必要性深入解讀

前面我們一再?gòu)?qiáng)調(diào)了equals方法重寫必須遵守的規(guī)則,接下來我們就是分析一個(gè)反面的例子,看看不遵守這些規(guī)則到底會(huì)造成什么樣的后果。

  1. package com.zejian.test;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /** * 反面例子 * @author zejian */
  5. public class AbnormalResult {
  6. public static void main(String[] args) {
  7. List<A> list = new ArrayList<A>();
  8. A a = new A();
  9. B b = new B();
  10. list.add(a);
  11. System.out.println("list.contains(a)->" + list.contains(a));
  12. System.out.println("list.contains(b)->" + list.contains(b));
  13. list.clear();
  14. list.add(b);
  15. System.out.println("list.contains(a)->" + list.contains(a));
  16. System.out.println("list.contains(b)->" + list.contains(b));
  17. }
  18. static class A {
  19. @Override
  20. public boolean equals(Object obj) {
  21. return obj instanceof A;
  22. }
  23. }
  24. static class B extends A {
  25. @Override
  26. public boolean equals(Object obj) {
  27. return obj instanceof B;
  28. }
  29. }
  30. }

上面的代碼,我們聲明了 A,B兩個(gè)類,注意必須是static,否則無法被main調(diào)用。B類繼承A,兩個(gè)類都重寫了equals方法,但是根據(jù)我們前面的分析,這樣重寫是沒有遵守對(duì)稱性原則的,我們先來看看運(yùn)行結(jié)果:

list.contains(a)->true

list.contains(b)->false

list.contains(a)->true

list.contains(b)->true

19行和24行的輸出沒什么好說的,將a,b分別加入list中,list中自然會(huì)含有a,b。但是為什么20行和23行結(jié)果會(huì)不一樣呢?我們先來看看contains方法內(nèi)部實(shí)現(xiàn)

  1. @Override
  2. public boolean contains(Object o) {
  3. return indexOf(o) != -1;
  4. }
進(jìn)入indexof方法
  1. @Override
  2. public int indexOf(Object o) {
  3. E[] a = this.a;
  4. if (o == null) {
  5. for (int i = 0; i < a.length; i++)
  6. if (a[i] == null)
  7. return i;
  8. } else {
  9. for (int i = 0; i < a.length; i++)
  10. if (o.equals(a[i]))
  11. return i;
  12. }
  13. return -1;
  14. }

可以看出最終調(diào)用的是對(duì)象的equals方法,所以當(dāng)調(diào)用20行代碼list.contains(b)時(shí),實(shí)際上調(diào)用了

b.equals(a[i]),a[i]是集合中的元素集合中的類型而且為A類型(只添加了a對(duì)象),雖然B繼承了A,但此時(shí)

a[i] instanceof B
結(jié)果為false,equals方法也就會(huì)返回false;而當(dāng)調(diào)用23行代碼list.contains(a)時(shí),實(shí)際上調(diào)用了a.equal(a[i]),其中a[i]是集合中的元素而且為B類型(只添加了b對(duì)象),由于B類型肯定是A類型(B繼承了A),所以
a[i] instanceof A
結(jié)果為true,equals方法也就會(huì)返回true,這就是整個(gè)過程。但很明顯結(jié)果是有問題的,因?yàn)槲覀兊?list的泛型是A,而B又繼承了A,此時(shí)無論加入了a還是b,都屬于同種類型,所以無論是contains(a),還是contains(b)都應(yīng)該返回true才算正常。而最終卻出現(xiàn)上面的結(jié)果,這就是因?yàn)橹貙慹quals方法時(shí)沒遵守對(duì)稱性原則導(dǎo)致的結(jié)果,如果沒遵守傳遞性也同樣會(huì)造成上述的結(jié)果。當(dāng)然這里的解決方法也比較簡(jiǎn)單,我們只要將B類的equals方法修改一下就可以了。
  1. static class B extends A{
  2. @Override
  3. public boolean equals(Object obj) {
  4. if(obj instanceof B){
  5. return true;
  6. }
  7. return super.equals(obj);
  8. }
  9. }

到此,我們也應(yīng)該明白了重寫equals必須遵守幾點(diǎn)原則的重要性了。當(dāng)然這里不止是list,只要是java集合類或者java類庫(kù)中的其他方法,重寫equals不遵守5點(diǎn)原則的話,都可能出現(xiàn)意想不到的結(jié)果。

5.為什么重寫equals()的同時(shí)還得重寫hashCode()

這個(gè)問題之前我也很好奇,不過最后還是在書上得到了比較明朗的解釋,當(dāng)然這個(gè)問題主要是針對(duì)映射相關(guān)的操作(Map接口)。學(xué)過數(shù)據(jù)結(jié)構(gòu)的同學(xué)都知道Map接口的類會(huì)使用到鍵對(duì)象的哈希碼,當(dāng)我們調(diào)用put方法或者get方法對(duì)Map容器進(jìn)行操作時(shí),都是根據(jù)鍵對(duì)象的哈希碼來計(jì)算存儲(chǔ)位置的,因此如果我們對(duì)哈希碼的獲取沒有相關(guān)保證,就可能會(huì)得不到預(yù)期的結(jié)果。在java中,我們可以使用hashCode()來獲取對(duì)象的哈希碼,其值就是對(duì)象的存儲(chǔ)地址,這個(gè)方法在Object類中聲明,因此所有的子類都含有該方法。那我們先來認(rèn)識(shí)一下hashCode()這個(gè)方法吧。hashCode的意思就是散列碼,也就是哈希碼,是由對(duì)象導(dǎo)出的一個(gè)整型值,散列碼是沒有規(guī)律的,如果x與y是兩個(gè)不同的對(duì)象,那么x.hashCode()與y.hashCode()基本是不會(huì)相同的,下面通過String類的hashCode()計(jì)算一組散列碼:

  1. package com.zejian.test;
  2. public class HashCodeTest {
  3. public static void main(String[] args) {
  4. int hash=0;
  5. String s="ok";
  6. StringBuilder sb =new StringBuilder(s);
  7. System.out.println(s.hashCode()+" "+sb.hashCode());
  8. String t = new String("ok");
  9. StringBuilder tb =new StringBuilder(s);
  10. System.out.println(t.hashCode()+" "+tb.hashCode());
  11. }
  12. }

運(yùn)行結(jié)果:

3548  1829164700

3548  2018699554

我們可以看出,字符串s與t擁有相同的散列碼,這是因?yàn)樽址纳⒘写a是由內(nèi)容導(dǎo)出的。而字符串緩沖sb與tb卻有著不同的散列碼,這是因?yàn)镾tringBuilder沒有重寫hashCode方法,它的散列碼是由Object類默認(rèn)的hashCode方法計(jì)算出來的對(duì)象存儲(chǔ)地址,所以散列碼自然也就不同了。那么我們?cè)撊绾沃貙懗鲆粋€(gè)較好的hashCode方法呢,其實(shí)并不難,我們只要合理地組織對(duì)象的散列碼,就能夠讓不同的對(duì)象產(chǎn)生比較均勻的散列碼。例如下面的例子:

  1. package com.zejian.test;
  2. public class Model {
  3. private String name;
  4. private double salary;
  5. private int sex;
  6. @Override
  7. public int hashCode() {
  8. return name.hashCode()+new Double(salary).hashCode()
  9. + new Integer(sex).hashCode();
  10. }
  11. }
上面的代碼我們通過合理的利用各個(gè)屬性對(duì)象的散列碼進(jìn)行組合,最終便能產(chǎn)生一個(gè)相對(duì)比較好的或者說更加均勻的散列碼,當(dāng)然上面僅僅是個(gè)參考例子而已,我們也可以通過其他方式去實(shí)現(xiàn),只要能使散列碼更加均勻(所謂的均勻就是每個(gè)對(duì)象產(chǎn)生的散列碼最好都不沖突)就行了。不過這里有點(diǎn)要注意的就是java 7中對(duì)hashCode方法做了兩個(gè)改進(jìn),首先java發(fā)布者希望我們使用更加安全的調(diào)用方式來返回散列碼,也就是使用null安全的方法Objects.hashCode(注意不是Object而是java.util.Objects)方法,這個(gè)方法的優(yōu)點(diǎn)是如果參數(shù)為null,就只返回0,否則返回對(duì)象參數(shù)調(diào)用的hashCode的結(jié)果。Objects.hashCode 源碼如下:
  1. public static int hashCode(Object o) {
  2. return o != null ? o.hashCode() : 0;
  3. }
因此我們修改后的代碼如下:
  1. package com.zejian.test;
  2. import java.util.Objects;
  3. public class Model {
  4. private String name;
  5. private double salary;
  6. private int sex;
  7. @Override
  8. public int hashCode() {
  9. return Objects.hashCode(name)+new Double(salary).hashCode()
  10. + new Integer(sex).hashCode();
  11. }
  12. }
java 7還提供了另外一個(gè)方法java.util.Objects.hash(Object... objects),當(dāng)我們需要組合多個(gè)散列值時(shí)可以調(diào)用該方法。進(jìn)一步簡(jiǎn)化上述的代碼:
  1. package com.zejian.test;
  2. import java.util.Objects;
  3. public class Model {
  4. private String name;
  5. private double salary;
  6. private int sex;
  7. // @Override
  8. // public int hashCode() {
  9. // return Objects.hashCode(name)+new Double(salary).hashCode()
  10. // + new Integer(sex).hashCode();
  11. // }
  12. @Override
  13. public int hashCode() {
  14. return Objects.hash(name,salary,sex);
  15. }
  16. }

好了,到此hashCode()該介紹的我們都說了,還有一點(diǎn)要說的如果我們提供的是一個(gè)數(shù)值類型的變量的話,那么我們可以調(diào)用Arrays.hashCode()來計(jì)算它的散列碼,這個(gè)散列碼是由數(shù)組元素的散列碼組成的。接下來我們回歸到我們之前的問題,重寫equals方法時(shí)也必須重寫hashCode方法。在Java API文檔中關(guān)于hashCode方法有以下幾點(diǎn)規(guī)定(原文來自java深入解析一書)。

  • 在java應(yīng)用程序執(zhí)行期間,如果在equals方法比較中所用的信息沒有被修改,那么在同一個(gè)對(duì)象上多次調(diào)用hashCode方法時(shí)必須一致地返回相同的整數(shù)。如果多次執(zhí)行同一個(gè)應(yīng)用時(shí),不要求該整數(shù)必須相同。

  • 如果兩個(gè)對(duì)象通過調(diào)用equals方法是相等的,那么這兩個(gè)對(duì)象調(diào)用hashCode方法必須返回相同的整數(shù)。

  • 如果兩個(gè)對(duì)象通過調(diào)用equals方法是不相等的,不要求這兩個(gè)對(duì)象調(diào)用hashCode方法必須返回不同的整數(shù)。但是程序員應(yīng)該意識(shí)到對(duì)不同的對(duì)象產(chǎn)生不同的hash值可以提供哈希表的性能。

通過前面的分析,我們知道在Object類中,hashCode方法是通過Object對(duì)象的地址計(jì)算出來的,因?yàn)镺bject對(duì)象只與自身相等,所以同一個(gè)對(duì)象的地址總是相等的,計(jì)算取得的哈希碼也必然相等,對(duì)于不同的對(duì)象,由于地址不同,所獲取的哈希碼自然也不會(huì)相等。因此到這里我們就明白了,如果一個(gè)類重寫了equals方法,但沒有重寫hashCode方法,將會(huì)直接違法了第2條規(guī)定,這樣的話,如果我們通過映射表(Map接口)操作相關(guān)對(duì)象時(shí),就無法達(dá)到我們預(yù)期想要的效果。如果大家不相信, 可以看看下面的例子(來自java深入解析一書)

  1. package com.zejian.test;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class MapTest {
  5. public static void main(String[] args) {
  6. Map<String,Value> map1 = new HashMap<String,Value>();
  7. String s1 = new String("key");
  8. String s2 = new String("key");
  9. Value value = new Value(2);
  10. map1.put(s1, value);
  11. System.out.println("s1.equals(s2):"+s1.equals(s2));
  12. System.out.println("map1.get(s1):"+map1.get(s1));
  13. System.out.println("map1.get(s2):"+map1.get(s2));
  14. Map<Key,Value> map2 = new HashMap<Key,Value>();
  15. Key k1 = new Key("A");
  16. Key k2 = new Key("A");
  17. map2.put(k1, value);
  18. System.out.println("k1.equals(k2):"+s1.equals(s2));
  19. System.out.println("map2.get(k1):"+map2.get(k1));
  20. System.out.println("map2.get(k2):"+map2.get(k2));
  21. }
  22. /**
  23. * 鍵
  24. * @author zejian
  25. *
  26. */
  27. static class Key{
  28. private String k;
  29. public Key(String key){
  30. this.k=key;
  31. }
  32. @Override
  33. public boolean equals(Object obj) {
  34. if(obj instanceof Key){
  35. Key key=(Key)obj;
  36. return k.equals(key.k);
  37. }
  38. return false;
  39. }
  40. }
  41. /**
  42. * 值
  43. * @author zejian
  44. *
  45. */
  46. static class Value{
  47. private int v;
  48. public Value(int v){
  49. this.v=v;
  50. }
  51. @Override
  52. public String toString() {
  53. return "類Value的值-->"+v;
  54. }
  55. }
  56. }
代碼比較簡(jiǎn)單,我們就不過多解釋了(注意Key類并沒有重寫hashCode方法),直接運(yùn)行看結(jié)果
  1. s1.equals(s2):true
  2. map1.get(s1):類Value的值-->2
  3. map1.get(s2):類Value的值-->2
  4. k1.equals(k2):true
  5. map2.get(k1):類Value的值-->2
  6. map2.get(k2):null
對(duì)于s1和s2的結(jié)果,我們并不驚訝,因?yàn)橄嗤膬?nèi)容的s1和s2獲取相同內(nèi)的value這個(gè)很正常,因?yàn)镾tring類重寫了equals方法和hashCode方法,使其比較的是內(nèi)容和獲取的是內(nèi)容的哈希碼。但是對(duì)于k1和k2的結(jié)果就不太盡人意了,k1獲取到的值是2,k2獲取到的是null,這是為什么呢?想必大家已經(jīng)發(fā)現(xiàn)了,Key只重寫了equals方法并沒有重寫hashCode方法,這樣的話,equals比較的確實(shí)是內(nèi)容,而hashCode方法呢?沒重寫,那就肯定調(diào)用超類Object的hashCode方法,這樣返回的不就是地址了嗎?k1與k2屬于兩個(gè)不同的對(duì)象,返回的地址肯定不一樣,所以現(xiàn)在我們知道調(diào)用map2.get(k2)為什么返回null了吧?那么該如何修改呢?很簡(jiǎn)單,我們要做也重寫一下hashCode方法即可(如果參與equals方法比較的成員變量是引用類型的,則可以遞歸調(diào)用hashCode方法來實(shí)現(xiàn)):
  1. @Override
  2. public int hashCode() {
  3. return k.hashCode();
  4. }
再次運(yùn)行:
  1. s1.equals(s2):true
  2. map1.get(s1):類Value的值-->2
  3. map1.get(s2):類Value的值-->2
  4. k1.equals(k2):true
  5. map2.get(k1):類Value的值-->2
  6. map2.get(k2):類Value的值-->2

6.重寫equals()中g(shù)etClass與instanceof的區(qū)別

雖然前面我們都在使用instanceof(當(dāng)然前面我們是根據(jù)需求(批次相同即相等)而使用instanceof的),但是在重寫equals() 方法時(shí),一般都是推薦使用 getClass 來進(jìn)行類型判斷(除非所有的子類有統(tǒng)一的語(yǔ)義才使用instanceof),不是使用 instanceof。我們都知道 instanceof 的作用是判斷其左邊對(duì)象是否為其右邊類的實(shí)例,返回 boolean 類型的數(shù)據(jù)。可以用來判斷繼承中的子類的實(shí)例是否為父類的實(shí)現(xiàn)。下來我們來看一個(gè)例子:父類Person

  1. public class Person {
  2. protected String name;
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. public Person(String name){
  10. this.name = name;
  11. }
  12. public boolean equals(Object object){
  13. if(object instanceof Person){
  14. Person p = (Person) object;
  15. if(p.getName() == null || name == null){
  16. return false;
  17. }
  18. else{
  19. return name.equalsIgnoreCase(p.getName ());
  20. }
  21. }
  22. return false;
  23. }
  24. }
子類 Employee
  1. public class Employee extends Person{
  2. private int id;
  3. public int getId() {
  4. return id;
  5. }
  6. public void setId(int id) {
  7. this.id = id;
  8. }
  9. public Employee(String name,int id){
  10. super(name);
  11. this.id = id;
  12. }
  13. /**
  14. * 重寫equals()方法
  15. */
  16. public boolean equals(Object object){
  17. if(object instanceof Employee){
  18. Employee e = (Employee) object;
  19. return super.equals(object) && e.getId() == id;
  20. }
  21. return false;
  22. }
  23. }
上面父類 Person 和子類 Employee 都重寫了 equals(),不過 Employee 比父類多了一個(gè)id屬性,而且這里我們并沒有統(tǒng)一語(yǔ)義。測(cè)試代碼如下:
  1. public class Test {
  2. public static void main(String[] args) {
  3. Employee e1 = new Employee("chenssy", 23);
  4. Employee e2 = new Employee("chenssy", 24);
  5. Person p1 = new Person("chenssy");
  6. System.out.println(p1.equals(e1));
  7. System.out.println(p1.equals(e2));
  8. System.out.println(e1.equals(e2));
  9. }
  10. }
上面代碼我們定義了兩個(gè)員工和一個(gè)普通人,雖然他們同名,但是他們肯定不是同一人,所以按理來說結(jié)果應(yīng)該全部是 false,但是事與愿違,結(jié)果是:true、true、false。對(duì)于那 e1!=e2 我們非常容易理解,因?yàn)樗麄儾粌H需要比較 name,還需要比較 ID。但是 p1 即等于 e1 也等于 e2,這是非常奇怪的,因?yàn)?e1、e2 明明是兩個(gè)不同的類,但為什么會(huì)出現(xiàn)這個(gè)情況?首先 p1.equals(e1),是調(diào)用 p1 的 equals 方法,該方法使用 instanceof 關(guān)鍵字來檢查 e1 是否為 Person 類,這里我們?cè)倏纯?instanceof:判斷其左邊對(duì)象是否為其右邊類的實(shí)例,也可以用來判斷繼承中的子類的實(shí)例是否為父類的實(shí)現(xiàn)。他們兩者存在繼承關(guān)系,肯定會(huì)返回 true 了,而兩者 name 又相同,所以結(jié)果肯定是 true。所以出現(xiàn)上面的情況就是使用了關(guān)鍵字 instanceof,這是非常容易導(dǎo)致我們“鉆牛角尖”。故在覆寫 equals 時(shí)推薦使用 getClass 進(jìn)行類型判斷。而不是使用 instanceof(除非子類擁有統(tǒng)一的語(yǔ)義)。

7.編寫一個(gè)完美equals()的幾點(diǎn)建議

下面給出編寫一個(gè)完美的equals方法的建議(出自Java核心技術(shù) 第一卷:基礎(chǔ)知識(shí)):

1)顯式參數(shù)命名為otherObject,稍后需要將它轉(zhuǎn)換成另一個(gè)叫做other的變量(參數(shù)名命名,強(qiáng)制轉(zhuǎn)換請(qǐng)參考建議5)

2)檢測(cè)this與otherObject是否引用同一個(gè)對(duì)象 :if(this == otherObject) return true;(存儲(chǔ)地址相同,肯定是同個(gè)對(duì)象,直接返回true)

3) 檢測(cè)otherObject是否為null ,如果為null,返回false.if(otherObject == null) return false;

4) 比較this與otherObject是否屬于同一個(gè)類 (視需求而選擇)

  • 如果equals的語(yǔ)義在每個(gè)子類中有所改變,就使用getClass檢測(cè) :if(getClass()!=otherObject.getClass()) return false; (參考前面分析的第6點(diǎn))

  • 如果所有的子類都擁有統(tǒng)一的語(yǔ)義,就使用instanceof檢測(cè) :if(!(otherObject instanceof ClassName)) return false;(即前面我們所分析的父類car與子類bigCar混合比,我們統(tǒng)一了批次相同即相等)

5) 將otherObject轉(zhuǎn)換為相應(yīng)的類類型變量:ClassName other = (ClassName) otherObject;

6) 現(xiàn)在開始對(duì)所有需要比較的域進(jìn)行比較 。使用==比較基本類型域,使用equals比較對(duì)象域。如果所有的域都匹配,就返回true,否則就返回flase。

  • 如果在子類中重新定義equals,就要在其中包含調(diào)用super.equals(other)

  • 當(dāng)此方法被重寫時(shí),通常有必要重寫 hashCode 方法,以維護(hù) hashCode 方法的常規(guī)協(xié)定,該協(xié)定聲明 相等對(duì)象必須具有相等的哈希碼 。


參考資料:
Java核心技術(shù) 第一卷:基礎(chǔ)知識(shí)

Java深入分析

http://wiki./project/java-enhancement/java-thirteen.html





























    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    日韩熟妇人妻一区二区三区| 亚洲欧美视频欧美视频| 伊人色综合久久伊人婷婷| 国产日韩中文视频一区| 亚洲欧美一二区日韩高清在线| 色婷婷国产熟妇人妻露脸| 亚洲内射人妻一区二区| 四十女人口红哪个色好看| 99日韩在线视频精品免费| 国产午夜精品美女露脸视频| 国产欧美日产久久婷婷| 亚洲中文字幕在线观看四区| 在线视频三区日本精品| 可以在线看的欧美黄片| 欧美精品一区久久精品| 色综合伊人天天综合网中文| 久久re6热在线视频| 国产午夜精品福利免费不| 日韩欧美三级视频在线| 国产一区二区三区四区中文| 日韩欧美一区二区久久婷婷| 亚洲少妇一区二区三区懂色| 亚洲中文字幕在线综合视频| 久久大香蕉精品在线观看| 日韩欧美一区二区久久婷婷| 欧美人妻少妇精品久久性色| 亚洲视频一级二级三级| 日韩人妻一区二区欧美| 亚洲中文字幕视频在线观看| 精品午夜福利无人区乱码| 国产精品蜜桃久久一区二区| 成人欧美一区二区三区视频| 国产老熟女乱子人伦视频| 国产永久免费高清在线精品| 成人精品视频一区二区在线观看| 日韩一区二区三区高清在| 久久精品a毛片看国产成人| 丝袜诱惑一区二区三区| 亚洲视频一区二区久久久| 日韩高清一区二区三区四区| 亚洲五月婷婷中文字幕|