目录
不覆盖equals方法" >不覆盖equals方法
覆盖equals方法" >覆盖equals方法
集合中equals方法的等价关系" >集合中equals方法的等价关系
如何编写equals方法" >如何编写equals方法
关于实现的equals需要注意的问题" >关于实现的equals需要注意的问题
首页 Java java教程 关于java覆盖equals更深层的方法概述

关于java覆盖equals更深层的方法概述

Apr 24, 2017 pm 04:07 PM

最近和同事谈到equals和==的区别。这其实是个非常老套简单的问题,但当你要亲自覆盖equals方法时,才发现,有一些你不知道却又不得不知道的事。覆盖equals,讲究很多。尽管Object是一个很具体的类,但是他的主要作用还是为了扩展。他的所有非final方法都有着明确的通用约定。因为他们被设计成要被覆盖的方法。任何一个类,在覆盖equals、hashCode、toString、clone、finalize时,都有责任遵守这些方法的通用约定。如果不能做到这一点,那么当多个类组合时将难以发挥我们期望的效果。

不覆盖equals方法


覆盖equals方法看起来简单,但是有许多覆盖方法会导致错误。最容易避免这种错误的方法就是不覆盖equals,这种情况下每个类的实例都只与自身相等。那么在什么情况下我们可以选择不覆盖equals方法呢?

类的每个实例本质上是唯一的

对于代表活动实体而不是值的类来说确实如此,比如每个线程实例。我们用equals方法比较他是毫无意义的,因为每个线程是唯一的。在这种情况下,我们不用覆盖equals方法,因为Object类中的equals已经完全够用了。

Object类中equals方法实现:

 public boolean equals(Object obj) { return (this == obj);  }
登录后复制
登录后复制

不关心类是否需要逻辑相等的判断

有些类是一些“数值类”,比较大小,数学运算是这些类的本职工作。在这种情况下,需要我们将类中存放的数值进行比较,需要进行逻辑相等的判断。除此之外的类,很大部分类没有“一个是否等于另外一个”的概念。这种不关心逻辑相等的类不需要覆盖equals方法。

超类实现的equals对子类适用

举个例子,继承了AbstractSet类的HashSet类在equals方法上并没有任何区别,那么HashSet直接使用AbstractSet的equals方法即可。

覆盖equals方法

与上面的内容相反的,我们需要覆盖equals方法的情况就是:如果类具有自己的逻辑相等的概念,并且父类没有进行可用的equals方法覆盖。这时需要我们亲自进行覆盖。

集合中equals方法的等价关系


equals方法实现了等价关系。离散数学中学习过等价关系的概念,对于一个R上的二元关系,如果它满足自反对称和传递,那么他就是等价的。我们来具体分析一下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。

自反性:∀ a ∈A, => (a, a) ∈ R
对称性:(a, b) ∈R∧ a ≠ b => (b, a)∈R
传递性:(a, b)∈R,(b, c)∈R =>(a, c)∈R

对比一下这三个性质,没有任何问题。由此可得,equals方法实现了等价关系。

如何编写equals方法


Object的equals方法只是简单的看看地址,这显然不可能满足我们的要求。那么在自己编写equals方法进行覆盖时如何才能保证编写出高质量的,逻辑比较的方法呢?equals的编写可以概括为下面四步:

1.使用==操作符检查参数是否只是对象的引用
如果结果相等则返回true,说明x与y是一个对象的不同引用,不需要再进行判断了。

2.使用instanceof操作符检查参数是否类型正确
如果结果不是正确的类型则返回false,因为我们的equals方法是继承自Object类的,所以参数的类型无法避免的是Object,我们先使用instanceof对参数进行类型判断,如果类型都不正确,就不用进行下一步判断了。

3.把参数转换成正确的类型
因为之前做了检测,所以这一步的类型转换没有问题。

4.对每个类中需要逻辑比较的域值进行判断
已经确保x和y是相同类型的不同实例,将需要判断的逻辑比较的域值取出进行比较判断即可。如果全部正确则返回true,否则返回false。

关于实现的equals需要注意的问题


当编写完equals方法后,一定要反复的判断是否符合自反性,对称性和传递性。不仅仅如此,在确保等价的情况下,编写equals方法时还有一些值得注意的事情,我们需要以此改进。

覆盖equals方法时总要覆盖hashCode方法
如果我们在编写有关散列的类时,必须在覆盖equals方法时覆盖hashCode方法。因为在散列表中,逻辑相同的对象应该具有相同的散列码。举个比较简单的例子:将String存入HashSet,有可能两个内容相同的String字串用==判断为false,但是他们在HashSet中只存在了一份。这就是因为逻辑相同的String拥有这相同的hashCode。
普遍性的,如果你为你的类覆盖了equals方法,那么证明在某种情况下会有两个不同对象是逻辑相等的。此时如果与散列相关,那么这两个对象需要相同的hashCode。所以覆盖equals方法时总要覆盖hashCode方法。

不要让equals方法过于智能
如果我们只是简单的按照上面的实现流程来编写equals的方法,既符合规定也不会导致奇怪的错误。但是如果我们非要去追求各种各样、花哨的等价关系,而把代码搞得臃肿不堪,那么既违反了高内聚的初衷,也会让代码出一些莫名其妙的错误。

不要将equals方法的参数类型弄错
说出来可能感觉好笑,但这确实是会发生的情况。修改了参数类型之后的equals方法已完全于Object类没有了关系,编译器不会报错,留下的只是给程序员无尽的头痛。如果不能意识到参数类型是Object,很有可能花费几个小时也搞不清程序为什么不能正常工作。

最近和同事谈到equals和==的区别。这其实是个非常老套简单的问题,但当你要亲自覆盖equals方法时,才发现,有一些你不知道却又不得不知道的事。覆盖equals,讲究很多。尽管Object是一个很具体的类,但是他的主要作用还是为了扩展。他的所有非final方法都有着明确的通用约定。因为他们被设计成要被覆盖的方法。任何一个类,在覆盖equals、hashCode、toString、clone、finalize时,都有责任遵守这些方法的通用约定。如果不能做到这一点,那么当多个类组合时将难以发挥我们期望的效果。

不覆盖equals方法


覆盖equals方法看起来简单,但是有许多覆盖方法会导致错误。最容易避免这种错误的方法就是不覆盖equals,这种情况下每个类的实例都只与自身相等。那么在什么情况下我们可以选择不覆盖equals方法呢?

类的每个实例本质上是唯一的

对于代表活动实体而不是值的类来说确实如此,比如每个线程实例。我们用equals方法比较他是毫无意义的,因为每个线程是唯一的。在这种情况下,我们不用覆盖equals方法,因为Object类中的equals已经完全够用了。

Object类中equals方法实现:

 public boolean equals(Object obj) { return (this == obj);  }
登录后复制
登录后复制

不关心类是否需要逻辑相等的判断

有些类是一些“数值类”,比较大小,数学运算是这些类的本职工作。在这种情况下,需要我们将类中存放的数值进行比较,需要进行逻辑相等的判断。除此之外的类,很大部分类没有“一个是否等于另外一个”的概念。这种不关心逻辑相等的类不需要覆盖equals方法。

超类实现的equals对子类适用

举个例子,继承了AbstractSet类的HashSet类在equals方法上并没有任何区别,那么HashSet直接使用AbstractSet的equals方法即可。

覆盖equals方法

与上面的内容相反的,我们需要覆盖equals方法的情况就是:如果类具有自己的逻辑相等的概念,并且父类没有进行可用的equals方法覆盖。这时需要我们亲自进行覆盖。

集合中equals方法的等价关系


equals方法实现了等价关系。离散数学中学习过等价关系的概念,对于一个R上的二元关系,如果它满足自反对称和传递,那么他就是等价的。我们来具体分析一下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。

自反性:∀ a ∈A, => (a, a) ∈ R
对称性:(a, b) ∈R∧ a ≠ b => (b, a)∈R
传递性:(a, b)∈R,(b, c)∈R =>(a, c)∈R

对比一下这三个性质,没有任何问题。由此可得,equals方法实现了等价关系。

如何编写equals方法


Object的equals方法只是简单的看看地址,这显然不可能满足我们的要求。那么在自己编写equals方法进行覆盖时如何才能保证编写出高质量的,逻辑比较的方法呢?equals的编写可以概括为下面四步:

1.使用==操作符检查参数是否只是对象的引用
如果结果相等则返回true,说明x与y是一个对象的不同引用,不需要再进行判断了。

2.使用instanceof操作符检查参数是否类型正确
如果结果不是正确的类型则返回false,因为我们的equals方法是继承自Object类的,所以参数的类型无法避免的是Object,我们先使用instanceof对参数进行类型判断,如果类型都不正确,就不用进行下一步判断了。

3.把参数转换成正确的类型
因为之前做了检测,所以这一步的类型转换没有问题。

4.对每个类中需要逻辑比较的域值进行判断
已经确保x和y是相同类型的不同实例,将需要判断的逻辑比较的域值取出进行比较判断即可。如果全部正确则返回true,否则返回false。

关于实现的equals需要注意的问题


当编写完equals方法后,一定要反复的判断是否符合自反性,对称性和传递性。不仅仅如此,在确保等价的情况下,编写equals方法时还有一些值得注意的事情,我们需要以此改进。

覆盖equals方法时总要覆盖hashCode方法
如果我们在编写有关散列的类时,必须在覆盖equals方法时覆盖hashCode方法。因为在散列表中,逻辑相同的对象应该具有相同的散列码。举个比较简单的例子:将String存入HashSet,有可能两个内容相同的String字串用==判断为false,但是他们在HashSet中只存在了一份。这就是因为逻辑相同的String拥有这相同的hashCode。
普遍性的,如果你为你的类覆盖了equals方法,那么证明在某种情况下会有两个不同对象是逻辑相等的。此时如果与散列相关,那么这两个对象需要相同的hashCode。所以覆盖equals方法时总要覆盖hashCode方法。

不要让equals方法过于智能
如果我们只是简单的按照上面的实现流程来编写equals的方法,既符合规定也不会导致奇怪的错误。但是如果我们非要去追求各种各样、花哨的等价关系,而把代码搞得臃肿不堪,那么既违反了高内聚的初衷,也会让代码出一些莫名其妙的错误。

不要将equals方法的参数类型弄错
说出来可能感觉好笑,但这确实是会发生的情况。修改了参数类型之后的equals方法已完全于Object类没有了关系,编译器不会报错,留下的只是给程序员无尽的头痛。如果不能意识到参数类型是Object,很有可能花费几个小时也搞不清程序为什么不能正常工作。

以上是关于java覆盖equals更深层的方法概述的详细内容。更多信息请关注PHP中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Java 中的完美数 Java 中的完美数 Aug 30, 2024 pm 04:28 PM

Java 完美数指南。这里我们讨论定义,如何在 Java 中检查完美数?,示例和代码实现。

Java中的Weka Java中的Weka Aug 30, 2024 pm 04:28 PM

Java 版 Weka 指南。这里我们通过示例讨论简介、如何使用weka java、平台类型和优点。

Java 中的史密斯数 Java 中的史密斯数 Aug 30, 2024 pm 04:28 PM

Java 史密斯数指南。这里我们讨论定义,如何在Java中检查史密斯号?带有代码实现的示例。

Java Spring 面试题 Java Spring 面试题 Aug 30, 2024 pm 04:29 PM

在本文中,我们保留了最常被问到的 Java Spring 面试问题及其详细答案。这样你就可以顺利通过面试。

突破或从Java 8流返回? 突破或从Java 8流返回? Feb 07, 2025 pm 12:09 PM

Java 8引入了Stream API,提供了一种强大且表达力丰富的处理数据集合的方式。然而,使用Stream时,一个常见问题是:如何从forEach操作中中断或返回? 传统循环允许提前中断或返回,但Stream的forEach方法并不直接支持这种方式。本文将解释原因,并探讨在Stream处理系统中实现提前终止的替代方法。 延伸阅读: Java Stream API改进 理解Stream forEach forEach方法是一个终端操作,它对Stream中的每个元素执行一个操作。它的设计意图是处

Java 中的时间戳至今 Java 中的时间戳至今 Aug 30, 2024 pm 04:28 PM

Java 中的时间戳到日期指南。这里我们还结合示例讨论了介绍以及如何在java中将时间戳转换为日期。

Java程序查找胶囊的体积 Java程序查找胶囊的体积 Feb 07, 2025 am 11:37 AM

胶囊是一种三维几何图形,由一个圆柱体和两端各一个半球体组成。胶囊的体积可以通过将圆柱体的体积和两端半球体的体积相加来计算。本教程将讨论如何使用不同的方法在Java中计算给定胶囊的体积。 胶囊体积公式 胶囊体积的公式如下: 胶囊体积 = 圆柱体体积 两个半球体体积 其中, r: 半球体的半径。 h: 圆柱体的高度(不包括半球体)。 例子 1 输入 半径 = 5 单位 高度 = 10 单位 输出 体积 = 1570.8 立方单位 解释 使用公式计算体积: 体积 = π × r2 × h (4

如何在Spring Tool Suite中运行第一个春季启动应用程序? 如何在Spring Tool Suite中运行第一个春季启动应用程序? Feb 07, 2025 pm 12:11 PM

Spring Boot简化了可靠,可扩展和生产就绪的Java应用的创建,从而彻底改变了Java开发。 它的“惯例惯例”方法(春季生态系统固有的惯例),最小化手动设置

See all articles