文档章节

小心覆盖equals

我是偶哦
 我是偶哦
发布于 2016/08/07 15:42
字数 1598
阅读 26
收藏 0
点赞 0
评论 5

我们知道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。对于数组,上述规则用到每一个元素中去。还有一个最佳实践是,先比较最可能不一致的,或者开销最低的内容。

© 著作权归作者所有

共有 人打赏支持
我是偶哦
粉丝 5
博文 32
码字总数 21607
作品 0
深圳
加载中

评论(5)

Mercy_丶
Mercy_丶

引用来自“我是偶哦”的评论

引用来自“Mercy_丶”的评论

引用来自“我是偶哦”的评论

引用来自“Mercy_丶”的评论

对称性的反面例子赶上去有点说不通,照理,x和y应该 instanceof的关系,所以y的equals方法也应该是忽略大小写的,所以.equals(x)也应该是true。
x是忽略大小写字符串类A的一个实例,y是普通区分大小写字符串B的一个实例,B extends A。
x1="hello"  y2="Hello" x1.equals(y2)和y2.equals(x1)一个为true,一个false。
就好像有颜色点和无颜色点比较,需不需要考虑颜色,站在有颜色点考虑需要,返回fasle,站在无颜色点考虑,不需要,返回true。

@我是偶哦 子类和父类完全是是不同的行为,没必要继承呀
拿字符串来举例,除了忽略大小写的区别外,其它例如split, toLowerCase等都是相同的,只有equals在判断两个字符串是否相等时才有区别,这也是继承带来的弊端之一。

@我是偶哦 总之,小心覆盖equals
我是偶哦
我是偶哦

引用来自“Mercy_丶”的评论

引用来自“我是偶哦”的评论

引用来自“Mercy_丶”的评论

对称性的反面例子赶上去有点说不通,照理,x和y应该 instanceof的关系,所以y的equals方法也应该是忽略大小写的,所以.equals(x)也应该是true。
x是忽略大小写字符串类A的一个实例,y是普通区分大小写字符串B的一个实例,B extends A。
x1="hello"  y2="Hello" x1.equals(y2)和y2.equals(x1)一个为true,一个false。
就好像有颜色点和无颜色点比较,需不需要考虑颜色,站在有颜色点考虑需要,返回fasle,站在无颜色点考虑,不需要,返回true。

@我是偶哦 子类和父类完全是是不同的行为,没必要继承呀
拿字符串来举例,除了忽略大小写的区别外,其它例如split, toLowerCase等都是相同的,只有equals在判断两个字符串是否相等时才有区别,这也是继承带来的弊端之一。
Mercy_丶
Mercy_丶

引用来自“我是偶哦”的评论

引用来自“Mercy_丶”的评论

对称性的反面例子赶上去有点说不通,照理,x和y应该 instanceof的关系,所以y的equals方法也应该是忽略大小写的,所以.equals(x)也应该是true。
x是忽略大小写字符串类A的一个实例,y是普通区分大小写字符串B的一个实例,B extends A。
x1="hello"  y2="Hello" x1.equals(y2)和y2.equals(x1)一个为true,一个false。
就好像有颜色点和无颜色点比较,需不需要考虑颜色,站在有颜色点考虑需要,返回fasle,站在无颜色点考虑,不需要,返回true。

@我是偶哦 子类和父类完全是是不同的行为,没必要继承呀
我是偶哦
我是偶哦

引用来自“Mercy_丶”的评论

对称性的反面例子赶上去有点说不通,照理,x和y应该 instanceof的关系,所以y的equals方法也应该是忽略大小写的,所以.equals(x)也应该是true。
x是忽略大小写字符串类A的一个实例,y是普通区分大小写字符串B的一个实例,B extends A。
x1="hello"  y2="Hello" x1.equals(y2)和y2.equals(x1)一个为true,一个false。
就好像有颜色点和无颜色点比较,需不需要考虑颜色,站在有颜色点考虑需要,返回fasle,站在无颜色点考虑,不需要,返回true。
Mercy_丶
Mercy_丶
对称性的反面例子赶上去有点说不通,照理,x和y应该 instanceof的关系,所以y的equals方法也应该是忽略大小写的,所以.equals(x)也应该是true。
覆盖equals方法与覆盖hashCode方法

有时候我们需要覆盖equals方法来满足一些需求 例如类具有自己特有的“逻辑概念”,但是它的超类还没有覆盖equals以实现期望的行为的时候,就需要类覆盖equals方法来完成。 equals方法实现了等...

Sgmder
2016/03/17
179
0
覆盖equals时请遵守通用约定(8)

1、覆盖equals方法需谨慎,可能会导致严重后果 最容易避免的方式就是,不去覆盖 2、覆盖equals 方法期望你满足: (1)类的每个实例本质上都是唯一的 代表活动实体而不是值得类确实如此 Obje...

职业搬砖20年
05/24
0
0
Java hashCode() 和 equals()的若干问题解答

本章的内容主要解决下面几个问题: 1、 equals() 的作用是什么? 2 、equals() 与 == 的区别是什么? 3、 hashCode() 的作用是什么? 4 、hashCode() 和 equals() 之间有什么联系? 第1部分 ...

刘诗书
2017/11/27
0
0
第8条 覆盖equals时请遵守通用约定

覆盖equals有时看起来很简单,但是许多覆盖方式会产生错误。一般来说需要满足以下条件: 1:类的每个实例本质上都是唯一的。 2:不关心类是否提供了“逻辑相等(logical equality)”的测试功...

李白吃白菜
2016/04/06
54
0
Java之hashCode()和equals()方法

程序世界和现实世界其实是一样的,相等和相同是不同的概念,就此简要说明一下其中的含义: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hash...

董广明
2014/04/01
0
3
第9条 覆盖equals时总要覆盖hashCode

大家在使用equals方法时,一个常见的错误根源就是重写了equals方法,而没有覆盖hashCode方法。 在每个覆盖equals方法的类中,也必须覆盖hashCode方法。如果不覆盖的话,就会违反Object.hash...

李白吃白菜
2016/04/08
74
0
hashCode() 和 equals()

第1部分 equals() 的作用 equals() 的作用是 用来判断两个对象是否相等。 equals() 定义在JDK的Object.java中。通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。源...

小菜鸡1
2016/08/17
15
0
java-“==”、equals和hashcode有什么区别

1)"=="运算符是比较两个变量的值是否相等。也就是说,该运算符用于比较变量对应的内存中所存储的值是否相等,要比较两个基础类型的数据或两个引用变量是否相等,只能使用"=="运算符。 具体而...

pointerException
2015/09/18
229
0
hashcode和equals的理解

属性值相等的两个对象,分别放进List和Set Set集合: 两个对象的equals和hashcode都相等,才认为是同一个对象; 如果equals为false,则不管hashcode什么结果,Set size为2; 如果equals为tru...

Nob
2014/08/19
0
0
hashCode与equals的区别与联系

一、equals方法的作用 1、默认情况(没有覆盖equals方法)下equals方法都是调用Object类的equals方法,而Object的equals方法主要用于判断对象的内存地址引用是不是同一个地址(是不是同一个对...

期待变强的菜鸟
2014/04/09
0
1

没有更多内容

加载失败,请刷新页面

加载更多

下一页

vue-router懒加载

1. vue-router懒加载定义 当路由被访问的时候才加载对应组件 2. vue-router懒加载作用 当构建的项目比较大的时候,懒加载可以分割代码块,提高页面的初始加载效率。 ###3. vue-router懒加载实...

不负好时光
6分钟前
0
0
庆祝法国队夺冠:用Python放一场烟花秀

天天敲代码的朋友,有没有想过代码也可以变得很酷炫又浪漫?今天就教大家用Python模拟出绽放的烟花庆祝昨晚法国队夺冠,工作之余也可以随时让程序为自己放一场烟花秀。 这个有趣的小项目并不...

猫咪编程
8分钟前
0
0
SpringBoot | 第七章:过滤器、监听器、拦截器

前言 在实际开发过程中,经常会碰见一些比如系统启动初始化信息、统计在线人数、在线用户数、过滤敏高词汇、访问权限控制(URL级别)等业务需求。这些对于业务来说一般上是无关的,业务方是无需...

oKong
22分钟前
4
0
存储结构分四类:顺序存储、链接存储、索引存储 和 散列存储

存储结构分四类:顺序存储、链接存储、索引存储 和 散列存储 存储结构分四类:顺序存储、链接存储、索引存储 和 散列存储。 顺序结构和链接结构适用在内存结构中。 顺序表每个单元都是按物理...

DannyCoder
32分钟前
0
0
Firefox 61已经为Ubuntu 提供支持

最新和最好的Mozilla Firefox 61 “Quantum”网络浏览器已经为Ubuntu Linux操作系统的用户提供了支持,现在可以通过官方软件库进行更新。 Mozilla于2018年6月26日发布了Firefox 61版本,该版...

六库科技
58分钟前
0
0
Win10升级后执行系统封装(Sysprep)报错

开始封装 一年多以前开始给公司封装Win10系统,便于统一给公司电脑初始化携带各种软件的系统,致力于装完既可以开发的状态。那时候最新的版本是Win10 1703版本,自然就以他为母盘,然后结合V...

lyunweb
今天
39
0
php 性能优化

#什么情况下会遇到性能问题 PHP 语法使用的不恰当

to_be_better
今天
0
0
Jenkins 构建触发器操作详解

前言 跑自动化用例每次用手工点击jenkins出发自动化用例太麻烦了,我们希望能每天固定时间跑,这样就不用管了,坐等收测试报告结果就行。 一、定时构建语法 * * * * * (五颗星,中间用空格隔...

覃光林
今天
0
0
IDEA配置技巧

超详细设置Idea类注释模板和方法注释模板 idea去掉注解param下划线 JetBrains全系列破解

AK灬
今天
0
0
rsync通过服务同步/Linux系统日志/screen工具

rsync通过服务同步 分为服务端(机器A) 和客户端(机器B) 机器A操作编辑/etc/rsyncd.conf配置文件 [root@yolks1 ~]# vim /etc/rsyncd.conf 文件中添加以下配置 port=873 ...

Hi_Yolks
今天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部