本章主要讨论的是如何规范的覆盖Object
的非final方法,以及如何实现Comparable
接口
第10条 覆盖equals
时请遵守通用约定
不需要覆盖equals
的情形
当类不覆盖equals
方法的时候,则比较的是对象的内存地址,则每一个对象仅与自身相等。以下几种情况不需要不该equals方法
- 本身就是每一个实例唯一(
Thread
) - 不太关心类是否提供了逻辑相等的测试功能(
Pattern
) - 父类已经覆盖了,并且父类的行为也完全适用于子类
- 类本身的访问级别是私有或者包可见。这时候应该将
equals
的实现调整为抛出AssertionError
。
覆盖equals
的约定
这里提到的几点看起来都是理所当然的,但是在不同的情形中,实现非常容易违反这些约定。尤其是在Collecion的使用中,如果没有完全遵守的话,非常容易出现问题。
自反性:对象必须等于自身
如果违反了这条约定,在一个实例添加进了Collection集合的时候,再调用containes方法会返回false
对称性:任何两个对象关于它们是否相等的结果保持一致
书中举的例子不太常见,是一个自定义的类在和传入的String
类进行比较的时候,会发生这样的问题
public final class CaseInsensitiveString {
private final String s;
public CaseInsensitiveString(String s) {
this.s = Objects.requireNonNull(s);
}
@Override public boolean equals(Object o) {
if (o instanceof CaseInsensitiveString)
return s.equalsIgnoreCase(
((CaseInsensitiveString) o).s);
if (o instanceof String) // One-way interoperability!
return s.equalsIgnoreCase((String) o);
return false;
}
}
这里在判断equals String的时候,大小写不敏感,但是反过来String equals一定会返回false。
传递性: 如果一个对象等于第二个对象, 第二个对象等于第三个对象, 则第一个对象一定等于第三个对象
这一条一般来说不会有问题,但在继承关系中非常容易出问题
这是一个Point
类的equals方法
public boolean equals(Object o) {
if (!(o instanceof Point))
return false;
Point p = (Point)o;
return p.x == x && p.y == y;
}
给Point
类加一个子类,添加一个Color属性,并且覆盖equals
public boolean equals(Object o) {
if (!(o instanceof ColorPoint))
return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
当子类equals父类的时候,就会是false
对于这种情形,作者也提到了我们没有办法在扩展实例化类的同时,既增加新的值组件,又能保留equals
约定
一种比较好的取巧方法是用组合结构替换继承结构
public class ColorPoint {
private Point point;
private Color color;
}
去掉了继承结构,这个问题就会迎刃而解
一致性:如果两个对象相等, 它们就必须始终保持相等, 除非它们被修改了
不要使equals
方法依赖不可靠的资源。比如java.net.URL
的equals
实现依赖了主机的IP地址,IP地址的变化概率非常高,很容易造成两次equals
调用结果不同
非空性
任何实例都不能equals
null的结果为true。
对于两个对象的比较,jdk官方提供了Objects.equals,比较了空指针的可能性。个人认为在比较的时候应该尽量使用这个官方方法进行比较。
满足约定的实现方式
- 使用
==
操作符检查参数是否为这个对象的引用, 如果是, 则返回true. - 使用
instanceof
操作符检查参数是否为正确的类型, 如果不是, 则返回false. 注意这个地方不能使用getClass
替换instanceof
- 把参数转换成正确的类型.
- 对于该类中的每个关键域, 检查参数中的域是否与该对象中对应的域相匹配.
目前Intellj提供了不同的jdk版本覆盖hashcode
和equals
方法的模板,直接使用就会比较标准的覆盖方法
注意点
- 覆盖
equals
必须同时覆盖hashcode
- 不要将
equals
的入参由Object替换为其它内容,因为这样是重载,并不是覆盖重写。应该养成良好的习惯,在覆盖方法的时候加上@Override
注解,及时发现这些错误
总结一下,这一节提到的很多特性,在使用作者最后提供的覆盖模板都可以很好的满足。 对于继承的equals覆盖,个人认为应该避免这种情况的出现,或许可以尝试一下序列化,不过感觉风险也很高。
在TMS中,我有几次覆盖equals的经历。在tariff这边有很多需求是需要将standard view的vo,group成pivot view,这时候就需要覆盖vo,在equals方法中判断除了container size type之外其它的字段是否相等
网友评论