TL; DR
==测试引用是否相等(它们是否是同一对象)
.equals()测试值是否相等(在逻辑上是否为“相等”)
equals()是用于比较两个对象是否相等的方法。当且仅当两个引用都指向同一实例时equals(),Object该类中方法的默认实现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)); // 打印错误 } }
即使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)); // 打印真实 } }
equals()如果对象的字段相同,则覆盖方法在此处确定对象相等。
注意,该hashCode()方法也被覆盖。该方法的约定规定,当两个对象相等时,它们的哈希值也必须相同。这就是为什么人们几乎总是必须超越hashCode()并equals()在一起。
特别注意方法的参数类型equals。是的Object obj,不是Foo obj。如果将后者放入您的方法中,则这不是该equals方法的替代。
在编写自己的类时,您必须在覆盖equals()和时编写类似的逻辑hashCode()。大多数IDE可以自动为您生成此代码。
equals()可以在String类中找到实现的示例,该类是核心Java API的一部分。String该类不是比较指针,而是比较的内容String。
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) { //...检查是否为空 if (getClass() != obj.getClass()) { return false; } //...比较字段 }
通常,通过比较类对象,如上所述完成此操作。但是,这在一些不太明显的特殊情况下可能会失败。例如,某些框架生成类的动态代理,而这些动态代理实际上是不同的类。这是使用JPA的示例。
Foo detachedInstance = ... Foo mergedInstance = entityManager.merge(detachedInstance); if (mergedInstance.equals(detachedInstance)) { //如果使用getClass()测试相等性,将永远无法到达这里 //因为mergedInstance是Foo的代理(子类) }
解决该限制的一种机制是使用 instanceof
@Override public final boolean equals(Object obj) { if (!(obj instanceof Foo)) { return false; } //...比较字段 }
但是,使用时必须避免一些陷阱instanceof。由于Foo可能具有其他子类,并且这些子类可能会覆盖,因此equals()您可能会遇到aFoo等于aFooSubclass但aFooSubclass不等于的情况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()并违反关键假设。