第三章 对于所有对象都通用的方法
本章将讲述何时以及如何覆盖Object类中的非final方法(equals、hashCode、toString、clone和finalize)。
第十条 覆盖equals时请遵守通用约定
覆盖equals方法看起来很简单,但是会许多覆盖方式会导致错误。
一 有时并不需要覆盖
有时,我们期望类的每个实例都只与它自身相等:
1 类的每个实例本质上都是唯一的
对于代表活动实体而不是值的类(如Thread)正是如此。
2 类没有必要提供“逻辑相等”的测试功能
设计者并不认为客户需要这样的功能(如Pattern)
3 父类已经覆盖了equals
并且父类的行为对于这个类也是合适的(如Set从Abstract继承equals实现)
4 类是私有的或包级私有的
这个类的equals方法永远不会被调用。
但是如果非常想规避风险,也可以选择覆盖equals方法,以确保它不会被意外调用:
@Override
public boolean equals(Object o) {
throw new AssertionError();
}
5 单例模式的类
对于singleton而言,逻辑相同和对象等同是一回事。
二 通用约定
当类具有自己特有的“逻辑相等”概念时,可以选择覆盖equals方法,这通常属于“值类”的情形(如Integer或者String)。
在覆盖equals方法时,必须遵守它的通用约定:
1 自反性(reflexive)
对于任何非 null 的引用值 x ,
x.equals(x) 必须返回 true 。
即对象必须等于其本身。
2 对称性(symmetric)
对于任何非 null 的引用值 x 和 y,
当且仅当 y.equals(x) 返回 true 时,
x.equals(y) 必须返回 true 。
任何两个对象对于“它们是否相等”的问题都必须保持一致。
3 传递性(transitive)
对于任何非 null 的引用值 x 、 y 和 z,
如果 x.equals(y) 返回 true,
并且 y.equals(z) 也返回 true 时,
x.equals(z) 必须返回 true 。
如果第一个对象等于第二个对象,而第二个对象又等于第三个对象,则第一个对象一定等于第三个对象。
4 一致性(consistent)
对于任何非 null 的引用值 x 和 y,
只要 equals 的比较操作在对象中所用的信息没有被修改,
多次调用应该一致的返回相同结果
如果两个对象相等,它们就必须始终保持相等,除非它们中有一个对象被修改了。
5 非空性(Non-nullity)
对于任何非 null 的引用值 x,x.equals(null) 必须返回 false。
所有对象都不能等于 null。
@Override
public boolean equals(Object o) {
if (! (o instanceof MyType)){
return false;
}
MyType mt = (MyType) o;
...
}
三 诀窍
1 使用 ==
使用 == 操作符检查“参数是否为这个对象的引用”。如果是,则返回 true。
2 使用 instanceof
使用 instanceof 操作符检查“参数是否为正确的类型”。如果不是,则返回false。
3 把参数转换成正确的类型
4 检查每个“关键”域
对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。
四 例子
public final class PhoneNumber {
private final short areaCode, prefix, lineNum;
public PhoneNumber(int areaCode, int prefix, int lineNum) {
this.areaCode = rangeCheck(areaCode, 999, "area code");
this.prefix = rangeCheck(prefix, 999, "prefix");
this.lineNum = rangeCheck(lineNum, 9999, "line num");
}
//检查范围
private static short rangeCheck(int val, int max, String arg) {
if(val < 0 || val > max) {
throw new IllegalArgumentException(arg + ":" + val);
}
return (short)val;
}
@Override
public boolean equals(Object o) {
if(o == this) {
return true;
}
if(!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNum == this.lineNum && pn.prefix == this.prefix && pn.areaCode == this.areaCode;
}
}
编写和测试equals方法都是十分繁琐的,最佳途径是使用 Google 开源的 AutoValue 框架,它会自动生成这些方法,通过类中的单个注解就能触发。
五 总结
不要轻易覆盖 equals 方法,除非迫不得已。
如果覆盖 equals 方法,一定要比较所有关键域,并查看它们是否遵守合约的五个条款。