equals() 方法
TL; DR
==
測試參考相等性(它們是否是同一個物件 )
.equals()
測試價值相等(它們在邏輯上是否 相等 )
equals()
是一種用於比較兩個物件是否相等的方法。當且僅當兩個引用都指向同一個例項時,Object
類中 equals()
方法的預設實現返回 true
。因此它與 ==
的比較表現相同。
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints false
}
}
儘管 foo1
和 foo2
是使用相同的欄位建立的,但它們指向記憶體中的兩個不同物件。因此,預設的 equals()
實現計算為 false
。
為了比較物件的內容是否相等,必須覆蓋 equals()
。
public class Foo {
int field1, field2;
String field3;
public Foo(int i, int j, String k) {
field1 = i;
field2 = j;
field3 = k;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 &&
field2 == f.field2 &&
(field3 == null ? f.field3 == null : field3.equals(f.field3));
}
@Override
public int hashCode() {
int hash = 1;
hash = 31 * hash + this.field1;
hash = 31 * hash + this.field2;
hash = 31 * hash + (field3 == null ? 0 : field3.hashCode());
return hash;
}
public static void main(String[] args) {
Foo foo1 = new Foo(0, 0, "bar");
Foo foo2 = new Foo(0, 0, "bar");
System.out.println(foo1.equals(foo2)); // prints true
}
}
這裡重寫的 equals()
方法決定了如果它們的欄位相同則物件是相等的。
請注意,hashCode()
方法也被覆蓋了。該方法的契約表明,當兩個物件相等時,它們的雜湊值也必須相同。這就是為什麼一個人必須幾乎總是一起覆蓋 hashCode()
和 equals()
。
特別注意 equals
方法的引數型別。它是 Object obj
,而不是 Foo obj
。如果你把後者放在你的方法中,那不是 equals
方法的覆蓋。
在編寫自己的類時,在覆蓋 equals()
和 hashCode()
時必須編寫類似的邏輯。大多數 IDE 都可以自動為你生成。
可以在 String
類中找到 equals()
實現的示例,該類是核心 Java API 的一部分。String
類不是比較指標,而是比較 String
的內容。
Version >= Java SE 7
Java 1.7 引入了 java.util.Objects
類,它提供了一種方便的方法 equals
,它比較了兩個潛在的 null
引用,因此它可以用來簡化 equals
方法的實現。
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Foo f = (Foo) obj;
return field1 == f.field1 && field2 == f.field2 && Objects.equals(field3, f.field3);
}
類比較
由於 equals 方法可以針對任何物件執行,因此該方法經常做的第一件事(檢查 null
之後)是檢查被比較物件的類是否與當前類匹配。
@Override
public boolean equals(Object obj) {
//...check for null
if (getClass() != obj.getClass()) {
return false;
}
//...compare fields
}
這通常通過比較類物件來完成。但是,在一些可能不明顯的特殊情況下,這可能會失敗。例如,一些框架生成類的動態代理,這些動態代理實際上是一個不同的類。以下是使用 JPA 的示例。
Foo detachedInstance = ...
Foo mergedInstance = entityManager.merge(detachedInstance);
if (mergedInstance.equals(detachedInstance)) {
//Can never get here if equality is tested with getClass()
//as mergedInstance is a proxy (subclass) of Foo
}
解決該限制的一種機制是使用 instanceof
比較類
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Foo)) {
return false;
}
//...compare fields
}
但是,使用 instanceof
時必須避免一些陷阱。由於 Foo 可能有其他子類,而那些子類可能會覆蓋 equals()
,你可能會遇到 Foo
等於 FooSubclass
但 FooSubclass
不等於 Foo
的情況。
Foo foo = new Foo(7);
FooSubclass fooSubclass = new FooSubclass(7, false);
foo.equals(fooSubclass) //true
fooSubclass.equals(foo) //false
這違反了對稱性和傳遞性的特性,因此是 equals()
方法的無效實現。因此,當使用 instanceof
時,一個好的做法是製作 equals()
方法 final
(如上例所示)。這將確保沒有子類覆蓋 equals()
並違反關鍵假設。