正確的equals實現

fairjm發表於2015-04-19

本文來自圖靈社群 fairjm

轉截請註明出處


首先是equals方法的Java API上的說明

  • It is reflexive: for any non-null reference value x, x.equals(x) should return true.
  • It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
  • For any non-null reference value x, x.equals(null) should return false.

遵循以上的原則是實現equals方法所必須的。


先說一下最後一條,也就是如果obj == null 就直接返回false


第一條自反性,實現這一條最簡單的方式就是用 this == obj 如果為true直接返回,沒什麼好說的。


第二條是對稱性,x.equals(y)的返回要和x.equals(y)的返回保持一致,那怎樣實現會不一致呢?這裡舉個例子,多型判等下:

package test;

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }

//省略getter setter hashCode

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        Person other;
        if (obj instanceof Person) {
            other = (Person) obj;
        } else {
            return false;
        }
        if (age != other.age)
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }

}

Worker:

package test;

public class Worker extends Person {
    private int workHours;

    public Worker(String name, int age, int workHours) {
        super(name, age);
        this.workHours = workHours;
    }
    // 省略getter setter hashCode

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        Worker other;
        if (obj instanceof Worker) {
            other = (Worker) obj;
        } else {
            return false;
        }
        if (workHours != other.workHours)
            return false;
        return true;
    }

    public static void main(String[] args) {
        Person p = new Person("cc", 20);
        Worker w = new Worker("cc", 20, 0);
        System.out.println(w.equals(p));
        System.out.println(p.equals(w));
    }

}

Worker繼承Person,多了一個屬性。上面程式碼的返回是:

false
true

和規範相悖,處理也很容易。把instanceof 改為判斷它們的class是否相等即可:if (obj.getClass() == getClass()) 。不同類的例項不應該相等,因為不同類的例項相等要做到自反是挺麻煩的,除非他們能擁有一樣的屬性,但如果兩個類的屬性都一樣那為什麼不把他們合併用一個類呢?


其實有了前兩條的規約和以上的實現,第三條可以不嚴謹推匯出來: x.equals(y) == true 有兩種情況,x == y(引用相等)或者x,y是同一個類的例項且屬性相等

  • 若 x == y 則,y.equals(z) -> x.equals(z)

  • 若 x,y同屬於一個類的例項且屬性相等 :

    y.equals(z) == true -> y == z(引用相等) 或者 y,z是同一個類的例項且屬性相等

    • y == z : x.equals(y) -> x.equals(z)

    • y,z是同一個類的例項且屬性相等 : x,y,z是同一個類的例項且屬性相等 -> x.equals(z) == true


第四條因為在判等上就是用的屬性和物件本身的資訊,在物件內的資訊不發生改變的情況下,運算保持一致,除非手欠在裡面用了一些會隨時間或呼叫次數發生改變的方法,簡單地說,就是保持equals方法是個純函式,只對傳入的引數進行運算而不改變任何狀態。


最簡單的實現方式就是讓IDE自動生成equalshashCode,在自己實現地情況下一定要注意對稱性是否滿足,因為它也是最容易出錯的。

相關文章