3.重新定义类里面的equals()方法
对象识别可能是一个很难解决的问题:
- 如果两个对象在内存中占据相同的位置,那么它们是相同的吗?
- 如果它们的id相同,它们是相同的吗?
- 或者如果所有的字段都相等呢?
虽然每个类都有自己的标识逻辑,但是在系统中有很多地方都需要去判断是否相等。例如,有如下的一个类,表示订单购买…
public class Purchase {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
}
代码中肯定有很多地方都是类似下面这样写法:
Purchase originalPurchase = new Purchase();
Purchase updatedPurchase = new Purchase();
if (originalPurchase.getId() == updatedPurchase.getId()) {
// Execute some logic for equal purchases
}
这些逻辑调用的越多(反过来,违背了DRY原则:不做重复的事(Don't Repeat Yourself)),Purchase类的身份信息也会变得越来越多。
如果出于某种原因,更改了Purchase类的身份逻辑(例如,更改了标识符的类型),则需要更新标识逻辑所在的位置肯定也非常多。
我们应该在类的内部初始化这个逻辑,而不是通过系统将Purchase类的身份逻辑进行过多的传播。乍一看,我们可以创建一个新的方法,比如isSame,这个方法的入参是一个Purchase对象,并对每个对象的id进行比较,看看它们是否相同:
public class Purchase {
private long id;
public boolean isSame(Purchase other) {
return getId() == other.gerId();
}
}
虽然这是一个有效的解决方案,但是忽略了Java对象Object自有的方法——equals方法。Java中的每个类都是继承了Object类,虽然是隐式的,因此同样也就继承了equals方法。默认情况下,此方法将检查对象标识(内存中相同的对象),如JDK中的对象类定义(version 1.8.0_131)中的以下代码片段所示:
public boolean equals(Object obj) {
return (this == obj);
}
以下这个equals方法充当了注入身份逻辑的自然位置(通过重写默认的equals实现):
public class Purchase {
private long id;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
else if (!(other instanceof Purchase)) {
return false;
}
else {
return ((Purchase) other).getId() == getId();
}
}
}
虽然这个equals方法看起来很复杂,但由于equals方法只接受类型对象的参数,所以我们只需要考虑三个案例:
- 另一个对象是当前对象(即originalPurchase.equals(originalPurchase)),根据定义,它们是同一个对象,因此返回true
- 另一个对象不是Purchase对象,在这种情况下,我们无法比较Purchase的id,因此,这两个对象不相等
- 其他对象不是同一个对象,但却是Purchase的实例,因此,是否相等取决于当前Purchase的id和其他Purchase是否相等
现在可以重构我们之前的条件,如下:
Purchase originalPurchase = new Purchase();
Purchase updatedPurchase = new Purchase();
if (originalPurchase.equals(updatedPurchase)) {
// Execute some logic for equal purchases
}
除了可以在系统中减少复制,重构默认的equals方法还有一些其它的优势。
例如,如果构造一个Purchase对象列表,并检查列表是否包含具有相同ID(内存中不同对象)的另一个Purchase对象,那么我们就会得到true值,因为这两个值被认为是相等的:
List<Purchase> purchases = new ArrayList<>();
purchases.add(originalPurchase);
purchases.contains(updatedPurchase); // True
通常,无论在什么地方,如果需要判断两个类是否相等,则只需要使用重写过的equals方法就可以了。如果希望使用由于继承了Object对象而隐式具有的equals方法去判断相等性,我们还可以使用= =操作符,如下:
if (originalPurchase == updatedPurchase) {
// The two objects are the same objects in memory
}
还需要注意的是,当equals方法被重写以后,hashCode方法也应该被重写。有关这两种方法之间关系的更多信息,以及如何正确定义hashCode方法,请自行检索。
重写equals方法不仅可以将身份逻辑在类的内部进行初始化,并在整个系统中减少了这种逻辑的扩散,它还允许Java语言对类做出有根据的决定。