深入equals方法

來源:互聯網
上載者:User

深入equals方法

equals方法的重要性毋須多言,只要你想比較的兩個對象不願是同一對象,你就應該實現
equals方法,讓對象用你認為相等的條件來進行比較.

下面的內容只是API的規範,沒有什麼太高深的意義,但我之所以最先把它列在這兒,是因為
這些規範在事實中並不是真正能保證得到實現.

1.對於任何參考型別, o.equals(o) == true成立.
2.如果 o.equals(o1) == true 成立,那麼o1.equals(o)==true也一定要成立.
3.如果 o.equals(o1) == true 成立且  o.equals(o2) == true 成立,那麼
  o1.equals(o2) == true 也成立.
4.如果第一次調用o.equals(o1) == true成立再o和o1沒有改變的情況下以後的任何次調用
都成立.
5.o.equals(null) == true 任何時間都不成立.

以上幾條規則並不是最完整的表述,詳細的請參見API文檔.

對於Object類,它提供了一個最最嚴密的實現,那就是只有是同一對象是,equals方法才返回
true,也就是人們常說的引用比較而不是值比較.這個實現嚴密得已經沒有什麼實際的意義,
所以在具體子類(相對於Object來說)中,如果我們要進行對象的值比較,就必須實現自己的
equals方法.

先來看一下以下這段程式:

    public boolean equals(Object obj)
    {
        if (obj == null) return false;
        if (!(obj instanceof FieldPosition))
            return false;
        FieldPosition other = (FieldPosition) obj;
        if (attribute == null) {
            if (other.attribute != null) {
                return false;
            }
        }
        else if (!attribute.equals(other.attribute)) {
            return false;
        }
        return (beginIndex == other.beginIndex
            && endIndex == other.endIndex
            && field == other.field);
    }

 這是JDK中java.text.FieldPosition的標準實現,似乎沒有什麼可說的.

 我信相大多數或絕大多數程式員認為,這是正確的合法的equals實現.畢竟它是JDK的API實現啊.

 還是讓我們以事實來說話吧:

package debug;

import java.text.*;

public class Test {
  public static void main(String[] args) {
    FieldPosition fp = new FieldPosition(10);
    FieldPosition fp1 = new MyTest(10);
    System.out.println(fp.equals(fp1));
    System.out.println(fp1.equals(fp));
  }
}
class MyTest extends FieldPosition{
  int x = 10;
  public MyTest(int x){
    super(x);
    this.x = x;
  }
  public boolean equals(Object o){
    if(o==null) return false;
    if(!(o instanceof MyTest )) return false;
    return ((MyTest)o).x == this.x;
  }
}

運行一下看看會列印出什麼:

System.out.println(fp.equals(fp1));列印true
System.out.println(fp1.equals(fp));列印flase

兩個對象,出現了不對稱的equals演算法.問題出在哪裡(腦筋急轉彎:當然出在JDK實現的BUG)?

我相信有太多的程式員(除了那些根本不知道實現equals方法的程式員外)在實現equals方法
時都用過instanceof運行符來進行短路最佳化的,實事求是地說很長一段時間我也這麼用過。
太多的教程,文檔都給了我們這樣的誤導。而有些稍有瞭解的程式員可能知道這樣的最佳化可能
有些不對但找不出問題的關鍵。另外一種極端是知道這個技術缺陷的骨灰級專家就提議不要這
樣應用。

我們知道,"通常"要對兩個對象進行比較,那麼它們"應該"是同一類型。所以首先利用instanceof
運行符進行短路最佳化,如果被比較的對象不和當前對象是同一類型則不用比較返回false,但事實
上,"子類是父類的一個執行個體",所以如果 子類 o instanceof 父類,始終返回true,這時肯定
不會發生短路最佳化,下面的比較有可能出現多種情況,一種是不能造型成子類而拋出異常,另一種
是父類的private 成員沒有被子類繼承而不能進行比較,還有就是形成上面這種不對稱比較。可能
會出現太多的情況。

那麼,是不是就不能用 instanceof運行符來進行最佳化?答案是否定的,JDK中仍然有很多實現是正
確的,如果一個class是final的,明知它不可能有子類,為什麼不用 instanceof來最佳化呢?

為了維護SUN的開發小組的聲譽,我不說明哪個類中,但有一個小組成員在用這個方法最佳化時在後加
加上了加上了這樣的注釋:

        if (this == obj)                      // quick check
            return true;
        if (!(obj instanceof XXXXClass))         // (1) same object?
            return false;
可能是有些疑問,但不知道如何做(不知道為什麼沒有打電話給我......)

那麼對於非final類,如何進行類型的quick check呢?

if(obj.getClass() != XXXClass.class) return false;

用被比較對象的class對象和當前對象的class比較,看起來是沒有問題,但是,如果這個類的子類
沒有重新實現equals方法,那麼子類在比較的時候,obj.getClass() 肯定不等於XXXCalss.class,
也就是子類的equals將無效,所以if(obj.getClass() != this.getClass()) return false;才是正
確的比較。

 

注意if(obj.getClass() != this.getClass()) return false;只是為了讓子類能正常工作,至於子類

繼承後如果正確地比較,不是父類應該考慮的,那是子類實現者提供的,事實上在子類中只在先調用

super.equals就能先比較父類的私人變數,然後子類中再比較子類擴充的類成員。在父類中利用動態

反射訪問子類的所有成員來比較,雖然可以達到讓子類直接使用equals的目的,但這違反了設計原則。

你為未來考慮得太遠。

 

 

 

另外一個quick check是if(this==obj) return true;

是否equals方法一定比較的兩個對象就一定是要同一類型?上面我用了"通常",這也是絕大多數程式
員的願望,但是有些特殊的情況,我們可以進行不同類型的比較,這並不違反規範。但這種特殊情況
是非常罕見的,一個不恰當的例子是,Integer類的equals可以和Sort做比較,比較它們的value是不
是同一數學值。(事實上JDK的API中並沒有這樣做,所以我才說是不恰當的例子)

在完成quick check以後,我們就要真正實現你認為的“相等”。對於如果實現對象相等,沒有太高
的要求,比如你自己實現的“人”類,你可以認為只要name相同即認為它們是相等的,其它的sex,
ago都可以不考慮。這是不完全實現,但是如果是完全實現,即要求所有的屬性都是相同的,那麼如
何實現equals方法?

class Human{
 private String name;
 private int ago;
 private String sex;
        ....................
        public boolean equals(Object obj){
  quick check.......
  Human other = (Human)ojb;
  return this.name.equals(other.name)
   && this.ago == ohter.ago
   && this.sex.equals(other.sex);
 }
}

 

關於equals方法的最後一點是:如果你要是自己重寫(正確說應該是履蓋)了equals方法,那同時就一
定要重寫hashCode().為是規範,否則.............
我們還是看一下這個例子:

public final class PhoneNumber {
    private final int areaCode;
    private final int exchange;
    private final int extension;

    public PhoneNumber(int areaCode, int exchange, int extension) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(exchange, 99999999, "exchange");
        rangeCheck(extension, 9999, "extension");
        this.areaCode = areaCode;
        this.exchange = exchange;
        this.extension = extension;
    }

    private static void rangeCheck(int arg, int max, String name) {
        if(arg < 0 || arg > max)
            throw new IllegalArgumentException(name + ": " + arg);
    }

    public boolean equals(Object o) {
        if(o == this)
            return true;
        if(!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber)o;
        return pn.extension == extension && pn.exchange == exchange && pn.areaCode == areaCode;
    }
}

注意這個類是final的,所以這個equals實現沒有什麼問題。

我們來測試一下:

    public static void main(String[] args) {
        Map hm = new HashMap();
        PhoneNumber pn = new PhoneNumber(123, 38942, 230);
        hm.put(pn, "I love you");
        PhoneNumber pn1 = new PhoneNumber(123, 38942, 230);
        System.out.println(pn);
        System.out.println("pn.equals(pn1) is " + pn.equals(pn1));
        System.out.println(hm.get(pn1));
        System.out.println(hm.get(pn));
    }
既然pn.equals(pn1),那麼我put(pn,"I love you");後,get(pn1)這什麼是null呢?
答案是因為它們的hashCode不一樣,而hashMap就是以hashCode為主鍵的。

所以規範要求,如果兩個對象進行equals比較時如果返回true,那麼它們的hashcode要求返回相等的值。

好了,休息,休息一下。。。。。。。。。。。。。。。。

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.