Object類中的equals方法用于檢測一個對象是否等于另一個對象。在Object類中,這個方法判斷兩個對象是否具有相同的引用,如果兩個對象具有相同的引用,它們一定是相等的。從這點上看,將其作為默認操作也是合乎情理的。然而,對于多數類類說,這種判斷并沒有什么意義,例如,采用這種方式比較兩個PrintStream是否相等就完全沒有意義。然而,經常需要檢測兩個對象狀態的相等性,如果兩個對象的狀態相等,就認為這兩個對象是相等的。所以一般在自定義類中都要重寫equals比較。
下面給出編寫一個完美equals()方法的建議:
(1)顯式參數命名為otherObject,稍后需要將轉換成一個叫other的變量
(2)檢測this與otherObject是否引用同一個對象:
1
|
if ( this ==otherObject) return true ; |
這條語句只是一個優化。實際上,這是一種經常采用的形式。因為計算這個等式要比一個一個地比較類中的域所付出的代價小的多。
(3)檢測otherObject是否為null,如果為null,返回false。這項檢測是很必要的。
1
|
if (otherObject== null ) return false ; |
(4)比較this和otherObject是否屬于同一個類,如果equals的語義在每個子類中有所改變,就使用getClass()檢測,它將自己作為目標類
1
|
if (getClass()!=otherObject.getClass()) return false ; |
如果所有的子類都擁有同一的語義,就使用instanceof檢測
1
|
if (!(otherObject instanceof ClassName)) return false ; |
(5)將otherObject轉換為相應類型的變量:
1
|
ClassName other=(ClassName)otherObject; |
(6)現在開始對所有需要比較的域進行比較。使用==比較基本類型域,使用equals比較對象域。如果所有域都匹配,就返回true,否則返回false;
1
|
return field1==other.field1&&field2.equals(other.field2) |
如果在子類中重新定義equals,就要在其中包含調用super.equals(other)。如果檢測失敗,就不可能相等。如果超類中的域相等,就比較子類中的實例域。
對于數組類型的域,可以使用靜態的Arrays.equals方法檢測相應的元素是否相等。
來看幾個字符串比較例子:
1
2
3
4
5
6
7
8
9
|
String a = "abc" ; String b = "abc" ; String c = new String( "abc" ); String d = new String( "abc" ); System.out.println(a == b); // true 因為JAVA中字符串常量是共享的,只有一個拷貝 System.out.println(a == c); // false a和c屬于2個不同的對象 System.out.println(a.equals(c)); // true 由于String對象的equals方法比較的是對象中的值,所以返回true。(和Object的equals方法不同) System.out.println(c==d); // false c和d雖然對象內的值相同,但屬于2個不同的對象,所以不相等 System.out.println(c.equals(d)); // true |
簡單的說,當比較字符串常量時,等于和equals返回的結果一樣,當想比較字符串對象的值時用equals。
看一個equals的使用例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
package chapter05.EqualsTest; import java.util.*; public class EqualsTest { public static void main(String[] args) { Employee alice1 = new Employee( "Alice Adams" , 75000 , 1987 , 12 , 15 ); Employee alice2 = alice1; // reference the same object 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))); System.out.println(bob.toString()); } } class Employee { public Employee(String n, double s, int year, int month, int day) { name = n; salary = s; GregorianCalendar calendar = new GregorianCalendar(year, month, day); hireDay = calendar.getTime(); } public String getName() { return name; } public double getSalary() { return salary; } public Date getHireDay() { return hireDay; } public void raiseSalary( double byPercent) { double raise = salary * byPercent / 100 ; salary += raise; } @Override 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 classed 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 hava identical values return name.equals(other.name) && salary == other.salary && hireDay.equals(other.hireDay); } @Override public int hashCode() { return 7 * name.hashCode() + 11 * new Double(salary).hashCode() + 13 * hireDay.hashCode(); } @Override public String toString() { return getClass().getName() + "[name=" + name + ",salary=" + salary + ",hireDay=" + hireDay + "]" ; } private String name; private double salary; private Date hireDay; } class Manager extends Employee { public Manager(String n, double s, int year, int month, int day) { super (n, s, year, month, day); bouns = 0 ; } @Override public double getSalary() { double baseSalary = super .getSalary(); return baseSalary + bouns; } public void setBouns( double b) { bouns = b; } @Override 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 bouns == other.bouns; } @Override public int hashCode() { return super .hashCode() + 17 * new Double(bouns).hashCode(); } @Override public String toString() { return super .toString() + "[bouns=" + bouns + "]" ; } private double bouns; } |
深入
下面根據“類是否覆蓋equals()方法”,將它分為2類。
(1) 若某個類沒有覆蓋equals()方法,當它的通過equals()比較兩個對象時,實際上是比較兩個對象是不是同一個對象。這時,等價于通過“==”去比較這兩個對象。
(2) 我們可以覆蓋類的equals()方法,來讓equals()通過其它方式比較兩個對象是否相等。通常的做法是:若兩個對象的內容相等,則equals()方法返回true;否則,返回fasle。
下面,舉例對上面的2種情況進行說明。
1. “沒有覆蓋equals()方法”的情況
代碼如下 (EqualsTest1.java):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
import java.util.*; import java.lang.Comparable; /** * @desc equals()的測試程序。 */ public class EqualsTest1{ public static void main(String[] args) { // 新建2個相同內容的Person對象, // 再用equals比較它們是否相等 Person p1 = new Person( "eee" , 100 ); Person p2 = new Person( "eee" , 100 ); System.out.printf( "%s\n" , p1.equals(p2)); } /** * @desc Person類。 */ private static class Person { int age; String name; public Person(String name, int age) { this .name = name; this .age = age; } public String toString() { return name + " - " +age; } } } |
運行結果:
結果分析
我們通過 p1.equals(p2) 來“比較p1和p2是否相等時”。實際上,調用的Object.java的equals()方法,即調用的 (p1==p2) 。它是比較“p1和p2是否是同一個對象”。
而由 p1 和 p2 的定義可知,它們雖然內容相同;但它們是兩個不同的對象!因此,返回結果是false。
2. "覆蓋equals()方法"的情況
我們修改上面的EqualsTest1.java:覆蓋equals()方法。
代碼如下 (EqualsTest2.java):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
import java.util.*; import java.lang.Comparable; /** * @desc equals()的測試程序。 */ public class EqualsTest2{ public static void main(String[] args) { // 新建2個相同內容的Person對象, // 再用equals比較它們是否相等 Person p1 = new Person( "eee" , 100 ); Person p2 = new Person( "eee" , 100 ); System.out.printf( "%s\n" , p1.equals(p2)); } /** * @desc Person類。 */ private static class Person { int age; String name; public Person(String name, int age) { this .name = name; this .age = age; } public String toString() { return name + " - " +age; } /** * @desc 覆蓋equals方法 */ @Override public boolean equals(Object obj){ if (obj == null ){ return false ; } //如果是同一個對象返回true,反之返回false if ( this == obj){ return true ; } //判斷是否類型相同 if ( this .getClass() != obj.getClass()){ return false ; } Person person = (Person)obj; return name.equals(person.name) && age==person.age; } } } |
運行結果:
結果分析:
我們在EqualsTest2.java 中重寫了Person的equals()函數:當兩個Person對象的 name 和 age 都相等,則返回true。
因此,運行結果返回true。
講到這里,順便說一下java對equals()的要求。有以下幾點:
對稱性:如果x.equals(y)返回是"true",那么y.equals(x)也應該返回是"true"。
反射性:x.equals(x)必須返回是"true"。
類推性:如果x.equals(y)返回是"true",而且y.equals(z)返回是"true",那么z.equals(x)也應該返回是"true"。
一致性:如果x.equals(y)返回是"true",只要x和y內容一直不變,不管你重復x.equals(y)多少次,返回都是"true"。
非空性,x.equals(null),永遠返回是"false";x.equals(和x不同類型的對象)永遠返回是"false"。
現在,再回顧一下equals()的作用:判斷兩個對象是否相等。當我們重寫equals()的時候,可千萬不好將它的作用給改變了!
equals() 與 == 的區別是什么?
== : 它的作用是判斷兩個對象的地址是不是相等。即,判斷兩個對象是不是同一個對象。
equals() : 它的作用也是判斷兩個對象是否相等。但它一般有兩種使用情況(前面第1部分已詳細介紹過):
情況1,類沒有覆蓋equals()方法。則通過equals()比較該類的兩個對象時,等價于通過“==”比較這兩個對象。
情況2,類覆蓋了equals()方法。一般,我們都覆蓋equals()方法來兩個對象的內容相等;若它們的內容相等,則返回true(即,認為這兩個對象相等)。
下面,通過示例比較它們的區別。
代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
import java.util.*; import java.lang.Comparable; /** * @desc equals()的測試程序。 */ public class EqualsTest3{ public static void main(String[] args) { // 新建2個相同內容的Person對象, // 再用equals比較它們是否相等 Person p1 = new Person( "eee" , 100 ); Person p2 = new Person( "eee" , 100 ); System.out.printf( "p1.equals(p2) : %s\n" , p1.equals(p2)); System.out.printf( "p1==p2 : %s\n" , p1==p2); } /** * @desc Person類。 */ private static class Person { int age; String name; public Person(String name, int age) { this .name = name; this .age = age; } public String toString() { return name + " - " +age; } /** * @desc 覆蓋equals方法 */ @Override public boolean equals(Object obj){ if (obj == null ){ return false ; } //如果是同一個對象返回true,反之返回false if ( this == obj){ return true ; } //判斷是否類型相同 if ( this .getClass() != obj.getClass()){ return false ; } Person person = (Person)obj; return name.equals(person.name) && age==person.age; } } } |
運行結果:
1
2
|
p1.equals(p2) : true p1==p2 : false |
結果分析:
在EqualsTest3.java 中:
(1) p1.equals(p2)
這是判斷p1和p2的內容是否相等。因為Person覆蓋equals()方法,而這個equals()是用來判斷p1和p2的內容是否相等,恰恰p1和p2的內容又相等;因此,返回true。
(2) p1==p2
這是判斷p1和p2是否是同一個對象。由于它們是各自新建的兩個Person對象;因此,返回false。