Java equals()的用法(附带实例)
Java 中的每个类都直接或间接地扩展了类 Object。当类没有显式超类时,它其实隐式扩展了 Object。
Object 类定义了一些适用于任何 Java 对象的方法,如下表所示:
equals() 方法测试一个对象是否与另一个对象相等。
equals() 方法是在 Object 类中实现的,用于确定两个对象的引用是否相同。如果两个对象完全相同,那么它们肯定是相等的,这是一个非常合理的默认行为。对于很多类来说,这样已经足够了。例如,比较两个 Scanner 对象的相等性几乎没有意义。
覆盖 equals() 方法仅用于基于状态的相等性测试,即两个对象如果具有相同内容则视为相等。例如,String 类覆盖了 equals(),通过检查两个字符串是否由相同的字符组成来判断相等。
注意,无论何时覆盖 equals() 方法,都必须提供一个兼容的 hashCode() 方法。
假设考虑 Item 类的两个对象,如果它们的描述和价格是相匹配的,那么将两者视为相等。下面是如何实现 equals() 方法:
当为子类定义 equals() 方法时,首先需要调用超类的 equals() 方法。如果该测试未通过,则对象不相等。如果超类的实例变量相等,则可以比较子类的实例变量。
当比较属于不同类的值时,equals() 方法应该如何操作?这是一个有争议的领域。在前面的示例中,如果类不完全匹配,那么 equals() 方法将返回 false。但许多编程人员会使用 instanceof 测试:
然而,这种比较通常不起什么作用。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:
Object 类定义了一些适用于任何 Java 对象的方法,如下表所示:
方法 | 功能描述 |
---|---|
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() 方法中完成的一些常规步骤:
- 很常见的情况是相等对象也是同一对象,且这种测试很高效;
- 与 null 进行比较时,每个 equals() 方法都需要返回 false;
- 由于 equals() 方法覆盖了 Object.equals,因此其参数的类型是 Object。需要将其强制转换为实际类型,以便查看对象的实例变量。在进行强制转换之前,使用 getClass() 方法或 instanceof 运算符进行类型检查。
- 最后,比较实例变量。对于基本类型,使用 ==。但是对于 double 值,如果关心 ±∞ 或 NaN,使用 Double.equals()。对于对象,使用 Objects.equals(),即 equals() 方法的空安全版本。调用 Objects.equals(x, y) 会在 x 为 null 时返回 false,而 x.equals(y) 将抛出异常。
如果实例变量是数组,可以使用静态 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() { ... } }