Java 经验法则8 复写equals时请遵守通用约定
以下情况大可不必要复写equals:
- 类的每个实例本质上都是唯一的(单例).
- 不关心类是否提供了逻辑相等的测试功能。
- 超类已经覆盖了equals,从超类继承过来的行为对于子类也合适。
- 类是私有的或包级私有的,可以确定它的equals方法永远不会被调用。在此无疑是应该做以下处理以防被意外调用。
而如果类具有自己特有的逻辑相等概念,不同于对象等同概念,而且超类还没覆盖equals以实现期望行为,这时我们就需要覆盖equals方法。
复写equals必须遵守的通用约定
-
自反性:对于任何非空实例x,x.equals(x)必须返回true; 如果违反这条,然后把该类的实例添加到集合中,该集合的contains方法将果断的告诉你,该集合不包含你刚刚添加的实例。
-
对称性:对于任何非空实例x和y,如果x.equals(y)返回true,那么y.equals(x)也必须返回true。 若无意违反了这条,这种情形倒是不难想象。例如考虑下面的类,它实现了一个区分大小写的字符串。字符串由toString保存,但在比较操作中被忽略。
假设我们有一个不区分大小写的字符串和一个普通字符串:
正如所料,cis.equals(s) 返回true,但问题在于,虽然CaseInsensitiveString类中的equals方法知道普通的字符串String对象,但是在String类中的equals方法却并不知道不区分到小写的字符串,因此,s.equals(cis)返回false,显然违反了对称性。
为了解决这个问题,只需要把企图与String互操作的这段代码从equals方法中去掉就可以了
- 传递性:对于任何非空实例x,y,z,只要x.equals(y)返回true,并且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
- 举个反面栗子:
假设你想扩展这个类,为一个点添加颜色信息:
如果不提供equals方法,而是直接从Point继承过来,在equals做比较时颜色信息就会被忽略。虽然不会违反约定,但是很明显这是无法接受。假设你编写了一个equals方法,只有当它的参数是另一个有色点,并具有相同位置和颜色时才返回true
这个方法问题在于,你在比较普通点和有色点时,以及相反情形时,可能会得到不同的结果。前一种比较忽略了颜色信息,后一种比较则总返回false,因为参数的类型不正确。这违反了对称性。
显然,p.equals(cp)返回true,cp.equals(p)返回falsely。你可以尝试来修正这个问题,让ColorPoint.equals在进行混合比较时忽略颜色信息
这样做确实提供了对称性,但是却牺牲了传递性
此时,p1.equals(p2)和p2.equals(p3)都返回true ,但是p1.equals(p3)则返回false,显然违反了传递性,前两种比较不考虑颜色信息,而第三站比较则考虑颜色信息。
事实上,这是面向对象语言中关于等价关系的一个基本问题。我们无法在扩展可实例化的类的同时,既增加新的组件,同时又保留equals约定。
比较好的解决方法就是只要不可能直接创建超类实例,前面所述种种问题都不会发生。或者超类和子类不同时实例化并且使用。
-
一致性:对于任何非空实例x和y,只要equals的比较操作在对象中所用信息没有被修改,多次调用x.equals(y)就会一致的返回true,或者一致地返回false。
-
非空性:对于任何非空的实例x,x.equals(null)必须返回false。 许多类的equals方法通过一个显式的null检测来防止这种情况。
其实这项检测是不必要的。为了检测其参数的同等性,equals方法必须先把参数转成适当的类型,以便可以调用它的访问方法或者属性,在进行转换之前,还必须使用instanceof操作符来判断类型。
如果漏掉这一步那么有可能会抛出ClassCastException异常。但是如果传递进来的参数为null,那么不管instanceof 哪种类型,都会返回false,所以,可以不用单独的null检测。
高质量equals编写:
- 使用==操作符检查参数是否为这个对象的引用
- 使用instanceof操作符检查参数是否正确地类型。
- 把参数转换成正确类型。
- 对于该类每个关键属性都进行比较。
注意:当包含很多关键属性时,在比较时应该最先比较最有可能不一致的属性,或者开销最低的属性。
编写完成后,应该问自己三个问题,是否对称的,是否传递的,是否一致的。
最后一些告诫:
- 复写equals时总要复写hashCode
- 不要企图让equals方法过于智能。
- 不要将equals声明中的Object对象参数替换成其他的类型。
例如:
为了避免上面错误,在方法前加上@Override注解就好,系统会给你检查。