博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
小心覆盖equals
阅读量:7216 次
发布时间:2019-06-29

本文共 2421 字,大约阅读时间需要 8 分钟。

  hot3.png

我们知道Object类中所有的非final方法equals、hashcode、toString、clone、finalize都有明确的约定,因为这些方法就是设计用来被子类覆盖的。如果不能按照约定覆盖,那么其他依赖这些方法的的类就无法正常工作,比如HashMap和HashSet。

我们先来讨论什么情况下不用覆盖equals方法:

  1. Object提供默认的equals方法可以表现出正确的行为(默认使用==判断);
  2. 客户端不关心逻辑上是否相等;
  3. 超类已经覆盖了equals方法,对子类来说工作的很好;
  4. 类是私有的或是包级私有的,这样的话客户端是不可能调用到equals方法的。

上述四种情况我们可以不用考虑覆盖equals方法,但有些类难免还是要覆写,让我们先看看有哪些约定的内容:

  1. 自反性 x.equals(x)为true
  2. 对称性 x.equals(y)=y.equals(x)
  3. 传递性 x.equals(y)=true,y.equals(z)=true,则x.equals(z)=true
  4. 一致性 只要equals中用到的信息没有修改,那么多次调用返回结果不变
  5. x.equals(null)返回false(注意x非空)

对我们来说上面的每一条都是很简单的,甚至认为本来就该这样。但事实上当我们要同时满足上述五条,有时候确是一个几乎不可能完成的任务。

自反性

自反性就是对于任何非null的引用值x,x.equals(x)必须返回true。这个应该很好理解,如果违反的话,最简单的你往集合里添加东西,然后调用contains,你会发现他会告诉你集合不包含你刚给添加的实例。

对称性

对于非空引用x,y,如果x.equals(y)返回true,那么y.equals(x)也必须返回true。简单来说就是x等于y,那么y就要等于x。举个例子看看违反的情况:如果x是一个不去分大小写的自定义字符串类的一个实例,那么x假设为“hello”,y为普通字符串”Hello“那么x.equals(y)应该返回true(x是自定义类,equals方法中调用了equalsIgnoreCase)。但是y.equals(x)返回false。

现在我们把x放到集合中,然后list.contains("Hello")会返回什么。true or false?

true也好false也好,你把希望都寄托在list.contains的内部实现上,内部遍历每个元素时,是使用x.equals(y),还是y.equals(x),而且这还会导致另一个问题,及时现在可以正确运行,那么未来的实现改变了怎么办,你的代码就不受你控制了。

传递性

传递性就是x等于y,y等于z,那么x也会等于z。同样,我们也举个例子来说明这个问题。现在我们有一个Point类,坐标系中的一个二维点,它的equals方法应该是比较该点的坐标(x,y),如果坐标相同则为同一个点。现在我们想要表示一个有颜色的点,我们继承了Point类,现在考虑equals方法。

首先父类的equals方法能用吗?答案很明显,不能。如果用父类的equals方法,那么颜色就不会比较,红点就会和绿点相同。于是我们覆写equals方法,加上颜色比较,问题结束了吗?

如果我们比较有色点和无色点会发生什么呢?父类会根据坐标判断是否相对,而子类只会返回false,因为缺少颜色信息,这样会不满足对称性的要求。我们再一次修正这个问题。我们在子类equals内部先判断类型(instanceof),如果是和父类型Point比较则不考虑颜色信息,和同类型ColorPoint则考虑颜色。这次把问题解决了吗?

我们再来考虑一种特殊情况,红点(ColorPoint),绿点(ColorPoint),点(Point)。假设三个点在同一个位置上,上面的方法我们会的到红点等于点,点等于绿点,但是红点不等于绿点。你是不是发现了什么,是的,我们又违反了传递性。

我们还可以继续改进用getClass判断类型,让不同类型比较都会返回false,即使是子类和父类比。但是这种方式也会带来一些麻烦,它会限制父类的覆用。

我们改如何办呢?事实上这是面向对象语言中关于等价关系的一个基本问题:我们无法在扩展可实例化的类的同时,既增加新的值组件,同时又保留equals约定,除非愿意放弃面向对象的抽象带来的优势。

在这种情况下一般使用组合,让ColorPoint持有Point是一种不错的选择,这种方法有点类似于getClass方法,但可以不用考虑继承带来的一些其他方面的副作用。

对于上述情况我们使可以避免的,我们可以把父类设计成接口或者抽象类,只要父类不和子类混用就不会出现问题。

一致性

简单来说就是相等的对象永远相等,不相等的永远不相等。要想满足这一点只要在equals中不要用不可靠的资源就行了。例如,java.net.URL这个类的equals方法使用了IP地址,主机名会映射到ip,也就是随着时间的推移equals结果可能发送变化。

非空性

如果不处理null,那么equals里会抛出异常。

equals使用建议

  1. 先判断传进来的是不是对象本身,这只是一种性能上的优化;
  2. 使用instanceof检查参数类型是否正确;
  3. 参数转换为正确的类型;
  4. 比较关键数据是否相等,相等为true,否则false。比较关键数据时,如果是对象用equals,如果是基本类型用==,但是基本类型中float和double是个例外,应该使用Float.compare方法和Double.compare方法,float和double有些特殊值比如Float.NaN,-0.0f。对于数组,上述规则用到每一个元素中去。还有一个最佳实践是,先比较最可能不一致的,或者开销最低的内容。

转载于:https://my.oschina.net/abely/blog/729185

你可能感兴趣的文章
***R
查看>>
Linux 源码编译安装mysql
查看>>
取消手机端页面长按图片出现保存或者图片被打开的方法
查看>>
关于图片居中问题
查看>>
并发下的死锁问题
查看>>
Winserver下的Hyper-v “未在远程桌面会话中捕获到鼠标”
查看>>
oracle体系结构基础
查看>>
有关TCP和UDP 粘包 消息保护边界
查看>>
Mono为何能跨平台?聊聊CIL(MSIL)
查看>>
安装scrapy问题:-bash:scrapy:command not found
查看>>
CentOS7 重置root密码
查看>>
博客作业四
查看>>
Scanner 输入---从键盘输入两个数进行相加
查看>>
test
查看>>
说无可说
查看>>
mysql 语句优化
查看>>
SCP 命令参数使用详解(最详细使用指南)
查看>>
windows cmd color setup
查看>>
一些问题
查看>>
ubuntu配置cudnn
查看>>