首页 > 编程笔记 > Java笔记 阅读:57

Java equals()的用法(附带实例)

Java 中的每个类都直接或间接地扩展了类 Object。当类没有显式超类时,它其实隐式扩展了 Object。

Object 类定义了一些适用于任何 Java 对象的方法,如下表所示:

表:java.lang.Object 类的方法
方法 功能描述
String toString() 生成此对象的字符串表示形式,默认情况下为类名和哈希码
boolean equals(Object other) 如果此对象被认为等于 other,则返回 true;如果与 other 不同或 other 为 null,则返回 false。默认情况下,如果两个对象相同,则它们相等。考虑空安全问题建议使用 Objects.equals(obj, other),而不是 obj.equals(other)
int hashCode() 生成此对象的哈希码。相等对象必须具有相同的哈希码。除非被覆盖,否则虚拟机会以某种方式分配哈希码
ClassgetClass() 生成描述该对象所属类的 Class 对象
protected Object clone() 复制此对象。默认情况下,副本是浅拷贝的
protected void finalize() 当垃圾收集器回收此对象时,将会调用此方法。不要覆盖它

equals() 方法测试一个对象是否与另一个对象相等。

equals() 方法是在 Object 类中实现的,用于确定两个对象的引用是否相同。如果两个对象完全相同,那么它们肯定是相等的,这是一个非常合理的默认行为。对于很多类来说,这样已经足够了。例如,比较两个 Scanner 对象的相等性几乎没有意义。

覆盖 equals() 方法仅用于基于状态的相等性测试,即两个对象如果具有相同内容则视为相等。例如,String 类覆盖了 equals(),通过检查两个字符串是否由相同的字符组成来判断相等。

注意,无论何时覆盖 equals() 方法,都必须提供一个兼容的 hashCode() 方法。

假设考虑 Item 类的两个对象,如果它们的描述和价格是相匹配的,那么将两者视为相等。下面是如何实现 equals() 方法:
public class Item {
   private String description;
   private double price;
   ...
   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 argument is null
      if (otherObject == null) return false;
      // Check that otherObject is an Item
      if (getClass() != otherObject.getClass()) return false;
      // Test whether the instance variables have identical values
      var other = (Item) otherObject;
      return Objects.equals(description, other.description) && price == other.price;
   }
   public int hashCode() { ... }
}
以下是需要在 equals() 方法中完成的一些常规步骤:

如果实例变量是数组,可以使用静态 Arrays.equals() 方法检查数组的长度是否相等以及对应的数组元素是否相等。


当为子类定义 equals() 方法时,首先需要调用超类的 equals() 方法。如果该测试未通过,则对象不相等。如果超类的实例变量相等,则可以比较子类的实例变量。
public class DiscountedItem extends Item {
   private double discount;
   ...
   public boolean equals(Object otherObject) {
      if (!super.equals(otherObject)) return false;
      var other = (DiscountedItem) otherObject;
      return discount == other.discount;
   }
   public int hashCode() { ... }
}
需要注意的是,如果 otherObject 不是 DiscountedItem,则超类中的 getClass 测试失败。

当比较属于不同类的值时,equals() 方法应该如何操作?这是一个有争议的领域。在前面的示例中,如果类不完全匹配,那么 equals() 方法将返回 false。但许多编程人员会使用 instanceof 测试:
if (!(otherObject instanceof Item)) return false;
这就会存在一种可能性,即 otherObject 可能属于子类。例如,可以比较 Item 和 DiscountItem。

然而,这种比较通常不起什么作用。eauals() 方法的一个要求是它是对称(symmetric)的:对于非空的 x 和 y,调用 x.equals(y) 和 y.equals(x) 需要返回相同的值。

现在假设 x 是 Item,y 是 DiscountedItem。由于 x.equals(y) 不考虑折扣,那么 y.equals(x) 也不能考虑折扣。

注意,Java API 包含 150 多个 equals() 方法的实现,其中混合了 instanceof 测试、调用 getClass、捕获 ClassCastException,当然也可能什么都不做。查看 java.sql.Timestamp 类的文档,实现者在文档中有些尴尬地指出,Timestamp 类继承自 java.util.Date,后者的 equals() 方法使用了 instanceof 测试,因此就无法覆盖 equals,使之同时做到对称且正确。

有一种情况下,instanceof 测试是有意义的:如果相等的概念在超类中是固定的,并且在子类中也从不改变。例如,如果我们按 ID 比较雇员,就会出现这种情况。在这种情况下,可以进行 instanceof 测试并将 equals() 方法声明为 final:
public class Employee {
   private int id;
   ...
   public final boolean equals(Object otherObject) {
      if (this == otherObject) return true;
      if (otherObject instanceof Employee other) return id == other.id;
      return false;
   }
   public int hashCode() { ... }
}

相关文章