1. 程式人生 > >認識Object中的幾個經常需要覆蓋的方法——equals方法

認識Object中的幾個經常需要覆蓋的方法——equals方法

      學習Java少不了對Object的認知,所有類都會繼承它的屬性,真正的超類。這一個系列,我會對Object中的幾個方法,也就是我們自定義類的時候需要重寫的幾個方法做一個介紹。下面是這一個系列的主要內容:

  • equals方法
  • hashCode方法
  • toString方法
  • clone方法
  • 自定義類時考慮實現Comparable介面

本系列內容源於對《Effective Java》中文第二版第8條到第12條的學習記錄。所有內容的準確性均以原書為準。

1,引言

      我想很多剛剛參加工作的java程式設計師在面試中都被問到過“==”和equals的區別,說實話,我以前不僅被問到過,而且還沒有很好的答上來,現在如果被問到,我想下面的答案應該是可以滿足基本要求

的:

(1) 在不重寫equals方法的時候,其和“==”功能是一樣的;

(2)在重寫了之後,它們的區別在於你是如何重寫equals方法的,通常的做法是用於比較兩個物件的值是否相同;我們重寫equals方法需要遵循以下幾條規則:

  • 自反性:對於任何非空引用值 xx.equals(x) 都應返回 true
  • 對稱性:對於任何非空引用值 xy,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true
  • 傳遞性:對於任何非空引用值 xyz,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z)
    應返回 true
  • 一致性:對於任何非空引用值 xy,多次呼叫 x.equals(y) 始終返回 true 或始終返回 false,前提是物件上 equals 比較中所用的資訊沒有被修改。
  • 對於任何非空引用值 xx.equals(null) 都應返回 false

(3)擴充套件:如果物件的hashCode值計算方法足夠優秀,我們可以直接通過比較物件的hashCode值作為equals方法的比較結果)

2,分析

  上面簡單的介紹了一下equals方法和“==”的區別,那我們就來看看每一條的分析:

(1)針對第一條,我們看一看Object中equals方法的原始碼:

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

我想也就不用再多說上面了,他們比較的也是兩個非空物件的引用

(2)重寫了equals方法之後,區別自然是你的equals方法是如何定義的,那下面就來仔細探討一下equals方法該如何定義:

  • 自反性  對於任何非空引用值 xx.equals(x) 都應返回 true
  • 對稱性 對於任何非空引用值 xy,當且僅當 y.equals(x) 返回 true 時,x.equals(y) 才應返回 true

這裡需要注意在出現繼承關係時的處理,比如一個Person類

package hfut.edu;

/**  * Date:2018年10月1日 上午11:05:45 Author:why  */

public class Person {

    int age;     String name;     String sex;

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

    @Override     public boolean equals(Object obj) {         // TODO Auto-generated method stub

        if (!(obj instanceof Person))             return false;

        Person p = (Person) obj;         return this.age == p.age && this.name.equals(p.name) && this.sex.equals(p.sex);

    }

}  

這裡面我們定義了三個成員變數並且重寫了equals方法,如果一個Student類繼承Person類並且新增加了兩個成員變數如下:

package hfut.edu; /** * Date:2018年10月1日 上午11:15:07 * Author:why */

public class Student extends Person {          int studentID;     String schoolName;

    public Student(int age, String name, String sex, int studentID,String SchoolName) {         super(age, name, sex);         this.studentID=studentID;         this.schoolName=schoolName;     }

}

現在,我想通過equals方法比較兩個學生,如果直接只用繼承Person類的,則新的成員變數比較不了,顯然不合適,如果重寫新增新的屬性比如:

@Override     public boolean equals(Object obj) {         if(!(obj instanceof Student))             return false;                  Student stu=(Student)obj;         return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;              }

使用測試程式有:

public class TestClass {     public static void main(String[] args) {

       Person p=new Person(26,"why","male");        Student stu=new Student(26,"why","male",2020,"hfut");                System.out.println("p.equals(stu)="+p.equals(stu));        System.out.println("stu.equals(p)="+stu.equals(p));     }

結果:

很顯然是違背了對稱性的,下面來看一下解決辦法,把Student類中的equals方法改成:

@Override     public boolean equals(Object obj) {         if(!(obj instanceof Person))             return false;             if(!(obj instanceof Student))             return obj.equals(this);         Student stu=(Student)obj;         return super.equals(obj)&&stu.schoolName.equals(this.schoolName)&&stu.studentID==this.studentID;              }  

結果:

這樣,對稱性的問題算是解決了。那麼,在Java的API裡面有沒有這樣錯誤示例了,下面我們就來看一個:

所以,在Java平臺類庫中是有違背equals約定的示例的。參考:

Date中equals原始碼:

 public boolean equals(Object obj) {         return obj instanceof Date && getTime() == ((Date) obj).getTime();     }

TimeStamp中equals原始碼:

  public boolean equals(Timestamp ts) {         if (super.equals(ts)) {             if  (nanos == ts.nanos) {                 return true;             } else {                 return false;             }         } else {             return false;         }     }

注:它們都是package java.sql包下面的

  • 傳遞性 對於任何非空引用值 xyz,如果 x.equals(y) 返回 true,並且 y.equals(z) 返回 true,那麼 x.equals(z) 應返回 true

就上面的例子,我們在測試一下這個特性是否滿足,測試程式和結果如下圖

很顯然,在這裡傳遞性失效了;所以有這麼一句話:

我們無法在擴充套件可例項化類的同時,既增加新的值元件,同時又保留equals約定,除非願意放棄面向物件的抽象所帶來的優勢。

對於上面的錯誤,我們可以使用複合的方式代替繼承的方式,也就是說吧Person類作為Student類的一個成員來操作,具體的實現我就不多說了;也比較簡單,主要是這種思想。

  • 一致性:對於任何非空引用值 xy,多次呼叫 x.equals(y) 始終返回 true 或始終返回 false,前提是物件上 equals 比較中所用的資訊沒有被修改

建議:不要使equals方法依賴於不可靠的資源。

由此可見,想重寫好Object中的equals方法還是不簡單的,比我們平時認為的肯定要複雜一些,下面是在重寫equals方法的時候的一些建議:

  • 使用“==”檢查引數是否為這個物件的引用
  • 使用instanceof 檢查引數是否為正確的型別
  • 把引數轉化為正確的型別
  • 檢查類中的每個關鍵域

對於float:使用Float.compare方法比較

對於double:使用Double.compare方法比較

對於其他基本型別:使用“”比較

對於引用型別域:使用equals方法(前提是引用指向的物件的類重寫了)

  • 覆蓋equals方法通常也需要覆蓋hashCode方法
  • 不要在equals中做太多額外的邏輯業務判斷,得不償失
  • 不要將equals中的引數Object換成其他的型別

說到這裡,其實關於Object中的equals方法的內容基本介紹完了,這裡面很多內容都沒有展開介紹。希望看到有模糊地方的朋友可以自己多展開一點。