这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
前言
覆盖equals方法很简单,但是什么时候该覆盖equals方法能说出来的就很少了。如果需要覆盖equals方法,那么应该写?
覆盖equals方法的时机
覆盖equals方法看起来似乎很简单,但是有许多覆盖方式会导致错误,并且后果非常严重,最容易避免这类问题的办法就是不覆盖equals方法,在这种情况下,类的每个实例都只与它自身相等。
不需要覆盖equals的情况
下面这几种情况就不需要覆盖equals()方法:
类的每个实例本质上都是唯一的。
对于代表活动实体而不是值(value)的类来说确实如此,例如Thread。Object提供的equals实现对于这些类来说是正确的行为;
我们可以看到Object的equals直接对应的就是和传入的对象比较引用,引用的地址是一样的,那肯定就是同一个对象。
不关心类是否提供了“逻辑相等(logical equality)”的测试功能。
如java.util.Random覆盖了equals,以检查两个Random实例是否产生相同的随机序列,但是调用者并不期望这样的功能。在这样的情况下,从Object继承得到的equals实现已经足够了;
超类已经覆盖了equals,从超类继承过来的行为对于子类来说也是合适的。
例如大多数Set实现都从AbstractSet继承equals实现,类似的有List和Map等;
类是私有的或是包级私有的,可以确定它的equals方法永远不会被调用。
永远都不会被调用,那就没必要重写了。
覆盖equals方法的规范写法
规范写法实际上在Object的equals方法的前面已经给好了
自反性:
对于任何非null的引用值x,x.equals(x)必须返回true;这一项很难被违反。。。
对称性:
对于任何非null的引用值x,y,当且仅当y.equals(x)返回true时,x.equals(y)也应该返回true;
传递性:
对于任何非null得引用值x、y和z,如果x.equals(y)返回true时,并且y.equals(z)也返回true,那么x.equals(z)也返回true;
一致性:
对于任何非null得引用值x和y,只要equals的比较操作在对象中所用的信息没有被修改,那么多次调用x.equals(y)就会一致的返回true,或者一致的返回false;千万不要编写依赖不可靠资源的 equals 方法,比如通过需要访问网络的才能实现的equals千万不能写,就依靠内存中的驻留对象进行确定性计算。
非空性:
对于任何非null的引用值x,x.equals(null)必须返回false;对于它,直接通过一个显式的null测试来防止这种情况的发生。
@Override
public boolean equals(Object o) {
if (o == null)
return false;
// ...
}
编写的技巧
- 使用==操作符检查“参数是否为这个对象的引用”;
- 使用instanceof操作符检查“参数是否为正确的类型”;
- 经过instanceof类型检查之后把参数转换成正确的类型;
- 对于该类中的每个“关键”域,检查参数中的域是否与该对象中对应的域相匹配。对于不是double和float的基本类型,可以使用==进行比较,对于引用类型,可以递归调用equals方法,对于float域,可以使用Float.compare方法,对于double域,可以使用Double.compare方法;
- 当编写完equals方法时,应该问自己三个问题:它是否满足对称性、传递性、以及一致性;
- 覆盖equals方法总要覆盖hashCode()方法;
- 不要将所覆盖的equals方法中的入参Object对象替换成其他对象,这样会重载,应该使用@Override注解。
// 参数类型必须是 Object
public boolean equals(MyClass o) {
//...
}
结论
能不重写就不重写,写,就会出错,用原来的最好!当面对equals方法时,应该根据覆盖equals方法的时机去判断是否需要覆盖equals方法,如果需要覆盖equals方法时,要严格遵守equals方法的规范。