1.equal方法
Object類中的equal方法用於檢測一個對象是否等於另外一個對象。在Object類中,這個方法將判斷兩個對象是否具有相同的引用。如果兩個對象具有相同的引用,它們一定是相等的。然而對於多數類來說,這種判斷並沒有什么意義,因為經常需要檢測兩個對象狀態是否相等,如果兩個對象的狀態相等,就認為這兩個狀態是相等的。
先來看一下Object類中的equals方法的源碼:
* @param a an object * @param b an object to be compared with {@code a} for equality * @return {@code true} if the arguments are equal to each other * and {@code false} otherwise * @see Object#equals(Object) */ public static boolean equals(Object a, Object b) { return (a == b) || (a != null && a.equals(b)); }
通過@see這個注釋,再去看一下調用的equals方法
* @param obj the reference object with which to compare. * @return {@code true} if this object is the same as the obj * argument; {@code false} otherwise. * @see #hashCode() * @see java.util.HashMap */ public boolean equals(Object obj) { return (this == obj); }
下面有一個實際的例子,Employee類是Manager類的父類,那么根據現實中的情況,如何判斷兩個雇員是否相等呢,那肯定是如果兩個雇員對象的姓名、薪水和雇佣日期都一樣,就認為他們是相等的。(為什么不會有同年同月同日...你是個杠精把)
看一下Employee類中判斷兩個雇員是否相等的方法:
public boolean equals(Object otherObject) { // a quick test to see if the objects are identical if (this == otherObject) return true; // must return false if the explicit parameter is null if (otherObject == null) return false; // if the classes don't match, they can't be equal if (getClass() != otherObject.getClass()) return false; // now we know otherObject is a non-null Employee Employee other = (Employee) otherObject; // test whether the fields have identical values return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay); }
如果這兩個對象相等,那么肯定相等。如果傳進來的對象變量為null,那么肯定不相等。如果兩個對象不屬於同一個類,那么肯定不相等。最后一種情況就很關鍵了,可能會有兩個對象變量的name和hireDay都為null的情況,這個時候就需要調用Object的equals方法了,通過源碼可以看出,
- 如果兩個對象變量的name或hireDay都為null,則調用Objects.equals方法會返回true,
- 如果其中只有一個對象變量的name或hireDay為null,即Objects.equals(a, b)中只有一個為null,那直接返回false
- 如果兩個對象變量的name或hireDay都不為null,即Objects.equals(a, b)中兩個都不為null,這時根據return (a == b) || (a != null && a.equals(b));可以知道需要調用a.equals(b)方法判斷兩個域是否相等。
子類Manager類中的equals方法為:即如果父類的equals方法檢測不相等,那么對象就不可能相等。如果父類中的域都相等,就繼續檢測子類中的實例域。
public boolean equals(Object otherObject) { if (!super.equals(otherObject)) return false; Manager other = (Manager) otherObject; // super.equals checked that this and other belong to the same class return bonus == other.bonus; }
測試代碼和輸出:
public static void main(String[] args) { Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee alice2 = alice1; Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); System.out.println("alice1 == alice2: " + (alice1 == alice2)); System.out.println("alice1 == alice3: " + (alice1 == alice3)); System.out.println("alice1.equals(alice3): " + alice1.equals(alice3)); System.out.println("alice1.equals(bob): " + alice1.equals(bob)); Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000);
System.out.println("carl.equals(boss): " + carl.equals(boss)); }
輸出為:
alice1 == alice2: true
alice1 == alice3: false
alice1.equals(alice3): true
alice1.equals(bob): false
carl.equals(boss): false
這里有一個問題,就是如果是通過if (getClass() != otherObject.getClass()) return false;來判斷,如果兩個對象不在一個類中,那么就判斷稱這兩個對象不相等。但是如果兩個參數不屬於同一個類,該如何處理呢,如果修改成if (!otherObject instanceof Employee) return false;(A instance B的意思是,如果A是B的一個實例或B的子類的一個實例,就返回true,否則返回false)這樣的話如果要滿足對稱性的要求的話會有一些問題,例如,e.equals(m)中的e是一個Employee對象,m是一個Manager對象,並且兩個對象具有相同的姓名、薪水和雇佣日期,如果在Employee.equals中調用instanceof檢測,則返回true,這沒問題。但是如果要反過來的話,如果要讓m.equals(e)也返回true,這就要求Manager類的equals方法必須能夠用自己與任何一個Employee對象進行比較,然而Manager類的對象還具有Employee類的對象沒有的獨特的實例域,這就使得使用instanceof出現了問題。
為了解決這個問題,可以從兩個方面考慮:(1)如果子類能夠擁有自己的相等概念,則對稱性需求將強制采用getClass進行檢測。(2)如果由父類決定相等的概念,那么就可以使用instanceof進行檢測,這樣可以在不同子類的對象之間進行相等的比較。
例如:如果兩個Manager對象所對應的姓名、薪水和雇佣日期均相等,但是獎金不相等,就認為它們不相同,因此,可以使用getClass檢測。如果使用雇員的ID作為相等檢測的標准,並且這個相等的概念適用於所有的子類,就可以使用instanceof檢測,並應該將Employee.equals聲明為final。
Java語言規范要求equals方法應該具有如下特性:
- 自反性:對於任何非空引用x,x.equals(x)應該返回true
- 對稱性:對於任何引用x和y,當且僅當y.equals(x)返回true,x.equals(y)也應該返回true
- 傳遞性:對於任何引用x和y,如果x.equals(y)返回true,y.equals(z)返回true,則x.equals(z)也應該返回ture
- 一致性:如果x和y引用的對象沒有發生變化,反復調用x.equals(y)應該返回同樣的結果
- 對於任意非空引用x,x.equals(null)應該返回false
一個完美的equals方法應該這樣編寫:
(1)顯示參數命名為otherObject
(2)檢測this與otherObject是否引用同一個對象:if(this == otherObject) return true;
(3)檢測otherObject是否為空:if(otherObject == null) return false;
(4)比較this與otherObject是否屬於同一個類:
- 如果equals的語義在每個子類中有所改變,就是用getClass檢測:if (getClass() != otherObject.getClass()) return false;
- 如果所有的子類都擁有統一的語義,就是用instanceof檢測:if(!otherObject instanceof ClassName)) return false;
(5)將otherObject轉換成相應的類類型變量:ClassName other = (ClassName)otherObject
(6)對所有需要比較的域進行比較,使用“==”比較基本類型域,使用Object.equals方法比較對象域,如果所有的域都匹配,就返回true,否則,返回false
(7)如果需要在子類中重新定義equals,要先調用父類的equals方法super.equals()
2.hashCode方法
散列碼(Hash Code)是由對象導出的一個整型值。散列碼是沒有規律的,如果x和y是兩個不同的對象,x.hashCode()與y.hashCode()基本上不會相同。
這里必須強調的是:equals方法和hashCode的定義必須一致:如果x.equals(y)返回true,那么x.hashCode()和y.hashCode()必須相等。
在Employee類中使用下面的代碼計算對象的hashCode值:為了實現兩個對象如果姓名、薪水和雇佣日期都相等那么這兩個對象相等,可以按照下面的 方法得到hashCode值,這里是因為相同字符串擁有相同的散列碼,因為字符串的散列碼是由內容導出的,有相同的計算公式。
public int hashCode() { return Objects.hash(name, salary, hireDay); }
Objects.hash方法會對各個參數調用hashCode方法,並組合這些散列值。
子類Manager類中的hashCode方法:
public int hashCode() { return java.util.Objects.hash(super.hashCode(), bonus); }
測試代碼和輸出:
public static void main(String[] args) { Employee alice1 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee alice2 = alice1; Employee alice3 = new Employee("Alice Adams", 75000, 1987, 12, 15); Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); Manager carl = new Manager("Carl Cracker", 80000, 1987, 12, 15); Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); System.out.println("alice1.hashCode(): " + alice1.hashCode()); System.out.println("alice3.hashCode(): " + alice3.hashCode()); System.out.println("bob.hashCode(): " + bob.hashCode()); System.out.println("carl.hashCode(): " + carl.hashCode()); } 輸出: alice1.hashCode(): -808853550 alice3.hashCode(): -808853550 bob.hashCode(): -624019882 carl.hashCode(): -2004699436
3.toString方法
Object中的toString方法用於返回表示對象值的字符串。
例如:
Employee中的toString方法:
public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]"; }
子類Manager中的toString方法:
public String toString() { return super.toString() + "[bonus=" + bonus + "]"; }
測試和輸出:
public static void main(String[] args) { Employee bob = new Employee("Bob Brandson", 50000, 1989, 10, 1); System.out.println("bob.toString(): " + bob); Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15); boss.setBonus(5000); System.out.println("boss.toString(): " + boss); } } 輸出: bob.toString(): aaaaTest.Employee[name=Bob Brandson,salary=50000.0,hireDay=1989-10-01] boss.toString(): aaaaTest.Manager[name=Carl Cracker,salary=80000.0,hireDay=1987-12-15][bonus=5000.0]