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()
并违反关键假设。