在Java类中,每个类都继承自Object类,它的可重写的方法中equals、hashCode、toString、clone等方法在实际应用中都会用到,而对于如何重写也是比较重要的,因为它会对HashMap、List之类的现有类的使用有影响。
equals方法
对于equals方法,在不重写时在以下几个情况中是可以的。
- 类的实例是唯一的。
- 不关心类是否存在逻辑相等。
- 父类已经重写equals方法,父类的逻辑对子类一样合适。
- 不会使用到equals方法。
这些也可以根据源码直接理解到。
1 | public boolean equals(Object obj) { |
源码的实现非常简单,其实就是当两个实例的内存地址相同时才相同,也就是说他们就是同一个实例。
而当我们需要实现逻辑相同的时候,这时候就需要重写equals方法。除枚举类型意外,因为枚举的每一个值值存在一个实例。除此之外还得重写hashCode方法,这个就在稍后再说。
重写了equals方法的类具有:
- 自反性:对于任何非null的引用x,都有x.equals(x)返回true
- 对称性:对于任何非null的引用x和y,当且仅当x.equals(y)返回true时,y.equals(x)也返回true
- 传递性:对于任何非null的引用x、y、z,如果x.equals(y)返回true且y.equals(z)返回true,那么x.equals(z)也必须返回true
- 一致性:对于任何非null的引用x和y,只要这两个引用未被修改,那么无论被调用多少次x.equals(y)都会一致的返回true,或者一致的返回false
- 非空性:对于任何非null的引用x,x.equals(null)必须返回false
所以对于重写equals方法的对象都可以用以上几条方法去验证,只要有不符合的那么重写的就有问题,那么就只能修改或者不用。
注意:我们无法在扩展可实例化的类的同时,既增加新的值,又保留equals约定。所以这也是建议使用复合而少用继承的原因之一。当然扩展不可实例化的类就不会有这样的问题,例如抽象类。
重写equals方法的技巧:
- 使用==操作符检查是否为这个对象的引用,是则返回true,这属于性能优化
- 使用instanceof检测类型是否正确,不正确则返回false
- 在执行instanceof之后转为正确的类型
- 对于每一个关键的字段都应该要检查是否匹配。
- 检测是否具有对称性、传递性以及一致性
重写equals方法的一些告诫:
- 重写equals方法时需要重写hashCode方法
- 不要将equals方法的参数类型Object改为其他类型。
hashCode方法
首先对于hashCode方法,只要重写了equals方法则就应该重写它;核心要求:需要保证两个对象equals返回true,则hashCode返回的数值也要相同;反之,hashCode相同equals不一定返回true,但是最好满足不同的对象也是不同的hashCode,那样能提高涉及到散列函数的效率。
这里有一个重写hashCode的模版:
1 | //hashCode的实现方式 |
上边f1到f6以及arr是7种不同的类型如何求hashCode的方式,对于对象就直接调用hashCode在加上31*result即可。
需要注意的是,hashCode中所用的关键字段应该与equals方法中判断的字段对应相同。避免最后结果不符合要求。
toString方法
这个方法看似不重要,但是在开发调试过程中能有正确的重写这个方法,能把打印对象的内容更简洁,利于调试。
谨慎覆盖clone方法
对于clone方法是protected的,我们在使用的时候要做到以下几点:
- 必须保证clone对象不会影响原始对象,并正确创建被克隆对象中的约束条件
- 如果该类是线程安全则需要对clone方法做好同步
- 对于可变的域,最好新建对象,递归赋值
- 如果不能提供良好的clone方法,最好用其他方法来代替对象拷贝,或不提供该功能
- 实现拷贝对象的好办法是提供拷贝构造器或拷贝工厂,也就是提供一个方法去构造一个新的对象,并赋值
考虑实现Comparable接口
实现了它,就能使该对象具有排序功能,便可使用Collections或者Arrays的sort方法,且不用去额外实现Comparator接口了。
对于实现这个接口,主要就是实现compareTo方法,其中返回值包含-1,0,1;1:表示大于,-1:表示小于,0:表示相同。越大的就在前边。
建议如果是表示相同,那么应该与equals方法中的比较一致