認識Object中的幾個經常需要覆蓋的方法——equals方法
學習Java少不了對Object的認知,所有類都會繼承它的屬性,真正的超類。這一個系列,我會對Object中的幾個方法,也就是我們自定義類的時候需要重寫的幾個方法做一個介紹。下面是這一個系列的主要內容:
- equals方法
- hashCode方法
- toString方法
- clone方法
- 自定義類時考慮實現Comparable介面
本系列內容源於對《Effective Java》中文第二版第8條到第12條的學習記錄。所有內容的準確性均以原書為準。
1,引言
我想很多剛剛參加工作的java程式設計師在面試中都被問到過“==”和equals的區別,說實話,我以前不僅被問到過,而且還沒有很好的答上來,現在如果被問到,我想下面的答案應該是可以滿足基本要求
(1) 在不重寫equals方法的時候,其和“==”功能是一樣的;
(2)在重寫了之後,它們的區別在於你是如何重寫equals方法的,通常的做法是用於比較兩個物件的值是否相同;我們重寫equals方法需要遵循以下幾條規則:
- 自反性:對於任何非空引用值
x
,x.equals(x)
都應返回true
。 - 對稱性:對於任何非空引用值
x
和y
,當且僅當y.equals(x)
返回true
時,x.equals(y)
才應返回true
。 - 傳遞性:對於任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,並且y.equals(z)
返回true
,那麼x.equals(z)
true
。 - 一致性:對於任何非空引用值
x
和y
,多次呼叫 x.equals(y) 始終返回true
或始終返回false
,前提是物件上equals
比較中所用的資訊沒有被修改。 - 對於任何非空引用值
x
,x.equals(null)
都應返回false
。
(3)擴充套件:如果物件的hashCode值計算方法足夠優秀,我們可以直接通過比較物件的hashCode值作為equals方法的比較結果)
2,分析
上面簡單的介紹了一下equals方法和“==”的區別,那我們就來看看每一條的分析:
(1)針對第一條,我們看一看Object中equals方法的原始碼:
public boolean equals(Object obj) { return (this == obj); }
我想也就不用再多說上面了,他們比較的也是兩個非空物件的引用
(2)重寫了equals方法之後,區別自然是你的equals方法是如何定義的,那下面就來仔細探討一下equals方法該如何定義:
- 自反性 對於任何非空引用值
x
,x.equals(x)
都應返回true
- 對稱性 對於任何非空引用值
x
和y
,當且僅當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包下面的
- 傳遞性 對於任何非空引用值
x
、y
和z
,如果x.equals(y)
返回true
,並且y.equals(z)
返回true
,那麼x.equals(z)
應返回true
。
就上面的例子,我們在測試一下這個特性是否滿足,測試程式和結果如下圖:
很顯然,在這裡傳遞性失效了;所以有這麼一句話:
我們無法在擴充套件可例項化類的同時,既增加新的值元件,同時又保留equals約定,除非願意放棄面向物件的抽象所帶來的優勢。
對於上面的錯誤,我們可以使用複合的方式代替繼承的方式,也就是說吧Person類作為Student類的一個成員來操作,具體的實現我就不多說了;也比較簡單,主要是這種思想。
- 一致性:對於任何非空引用值
x
和y
,多次呼叫 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方法的內容基本介紹完了,這裡面很多內容都沒有展開介紹。希望看到有模糊地方的朋友可以自己多展開一點。