本来要休息了,结果看到了一篇文章,读完之后大(shi)受(mian)震(le)撼。
本月的第22篇原创文章,努力加油!
在写这个篇文章的时候,我自认为这个东西应该没啥技术含量。实际上也是如此。
但是这里强调的是对于这个方法我们看到的本质:思考!
就和之前看到有个问题 :
你是什么时候感觉到自己的代码能力开始提升的?
其实代码能力的提升并不是会写代码了,而是一些书写习惯你再不断的优化。这种优化就是在提升代码能力了。
还是老薛一直强调了学习!=记忆
,代码能力 != 会写
。更多时候多些思考,和恍然大悟这才是核心。
那么我们一起来看看关于equals
方法有什么值得我们思考的点。
如何生成equals方法
大家现在写这个方法,会怎么做呢?
- 快捷键自动生成;
- 通过使用
lombok
的@EquaksAndHashCode
注解 - 在代码中使用
Objects.equals(this,other)
完成; - 直接
return
,比较几个对应的属性结束;
对于这些其实都没有问题的。我想带大家看的是,通过IDEA生成的equals方法和使用lombok生成的equals方法竟然逻辑不太一样。另外你如果注意观察看Java API的源码,你会发现他们也有所区别。
我们思考一个逻辑,按道理而言,不管是用工具自带的关键件也好,或者是组件的生成也罢。
既然能自动生成,那么一定是有一个对应的范式来规范代码内容。那么为什么API中还会有不同的呢?
产生equals方法大PK
针对于我们编写的一个类
class User{
private String name;
private Integer score;
private String school;
}
IDEA自动生成的equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name) && Objects.equals(score, user.score)
&& Objects.equals(school, user.school);
}
lombok生成的equals方法
public boolean equals(Object o) {
if (o == this) {
return true;
} else if (!(o instanceof User)) {
return false;
} else {
User other = (User)o;
if (!other.canEqual(this)) {
return false;
} else {
label47: {
Object this$score = this.score;
Object other$score = other.score;
if (this$score == null) {
if (other$score == null) {
break label47;
}
} else if (this$score.equals(other$score)) {
break label47;
}
return false;
}
Object this$name = this.name;
Object other$name = other.name;
if (this$name == null) {
if (other$name != null) {
return false;
}
} else if (!this$name.equals(other$name)) {
return false;
}
Object this$school = this.school;
Object other$school = other.school;
if (this$school == null) {
if (other$school != null) {
return false;
}
} else if (!this$school.equals(other$school)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(Object other) {
return other instanceof User;
}
大家看的时候,因为可能比较长,所以看起来稍微有点不痛快,我把不重要的代码修改一下,然后看:
[图片上传失败...(image-b62e7f-1692762840105)]
最核心的代码就在这里,左侧是IDEA自动生成的使用getClass比较,右侧是通过lombok生成的使用instanceof比较。
这只是一个例子,我们在看一个在Java API中也不太一样的例子。有一个很奇怪的例子,在java.sql.Timestamp这个类中,你会发现竟然有两个equals
方法,而这两个方法的参数竟然是父子关系。
public boolean equals(Timestamp ts) {
if (super.equals(ts)) {
if (nanos == ts.nanos) {
return true;
} else {
return false;
}
} else {
return false;
}
}
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;
}
}
这都是为什么呢? 为什么一个简单的equals
方法会如此的奇怪? 到底是为什么出现了这样的变化呢?
说明问题
instanceof和getClass到底怎么用?
为了更好的说明问题,我们这里通过一些代码的例子来说明这个问题。
我们现在有两个类,一个类是父类emp
员工类,还有一个类是manager
经理类。
在这样的情况下,我们知道对于任意两个相同的对象比如emp
,判定他们相等是一个很简单的过程。
我们认为两个员工的姓名相同、年龄相同,在这样的情况下的我们就认为他们是相等的。
所以我们的代码大致应该是这样的:
@Getter
@Setter
public class Emp{
protected String name;
protected Integer age;
}
@Getter
@Setter
public class Manager extends Emp{
protected Integer bonus;
}
那么我们如何判定两个emp
对象是否相等呢?
我们编写方法如下:
@Override
public boolean equals(Object obj){
// 如果两个对象的地址相同 则直接返回true
if (this == obj)
return true;
// 因为要进行比较 所以判定传入对象是否为null 不为null 往下走
// 如果为null 则直接返回false
if (obj == null)
return false;
// 继续判定传入对象的class对象是否一致 不一致则直接返回false
if (this.getClass() != obj.getClass())
return false;
// 接下来能够确定传入的对象就是一个emp对象
Emp otherObj = (Emp)obj;
// 判定对应的每个属性的值
return this.name.equals(otherObj.name)
&&this.age == otherObj.age;
}
这个代码最后的返回结果还是有些小瑕疵,因为每次都需要考虑对象属性为null的情况,所以建议修改为如下:
return Objects.equals(this.name,otherObj.name)
&&Objects.equals(this.age,otherObj.age);
针对与这个代码,我们一起来考虑一下,为什么这里比较的使用getClass
而不是使用instanceof
?
这里原因就在与如果出现了父子类的情况呢?
啥?没理解? 看代码!!!
[图片上传失败...(image-c3e958-1692762840105)]
我们只需要把红色框框的代码互相掉个个,然后测试即可。
你会发现我们通过下面的代码进行测试,但是得到的结果是不同的。
Emp emp = new Emp();
emp.name = "zhangsan";
emp.age = 123;
Manager manager = new Manager();
manager.name = "zhangsan";
manager.age = 123;
System.out.println(emp.equals(manager));
- 使用
getClass
最后的结果是false
; - 使用
instanceof
最后的结果是true
;
你没有看错,原因估计你也猜到了就是因为instanceof
判定会把子类判定父类类型得到的结果也是true
。
所以导致的结果就是:
Instanceof 不光没有解决传入对象是子类的问题,并且还引出来了程序的其他问题。
如果你习惯性的在代码中使用instanceof
来判定的话,是不是需要注意了呢?
那么到底什么时候用instanceof
,什么时候用getClass
呢?
在《Java核心技术卷 卷1》中给出的是:
1: 如果子类能够拥有自己相等概念,则对称性需求需要强制采用getClass
进行检测;
2: 如果通过父类来确定相等的概念,则使用instanceof
来进行检测,这样可以在不同的子类对象中进行相等比较。
后面的一些彩蛋,关于上面的员工和经理的例子中,假设我们判定是否相等是查看当前员工的编号,那么此时* equals****方法应该就声明为****final****,并且内部只需要通过****instanceof****来进行判定;但是如果经理中姓名、年龄、奖金都相等我们才认为这两个经理相等,那么就需要通过****getClass****进行判定。*
为什么会出现两个equals方法
关于equals
方法,Java语言规范要求:
- (1)自反性:对于任何非空引用,x.equals(x)应该返回true;
- (2)对称性:对于任何引用x和y,当且仅当y.equals(x)返回true,x.equals(y)也应该返回true;
- (3)传递性:对于任何引用x、y和z,如果x.equals(y)返回true,y.equals(z)返回true,x.equals(z)也应该返回true;
- (4)一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该返回同样的结果;
- (5)对于任意非空引用x,x.equals(null)应该返回false。
当然这个东西都很好理解,其实关于我们上文说的内容,最核心的就是 :
父类.equals(子类) 和子类.equals(父类) 的对称性一定要满足。
但是在Timestamp
类中,因为它继承了Date
,而在Date
中使用了instanceof
就像我们上文说的,就会出现对称性得不到满足。所以为了保证这一点,不得不在自己的类中,声明了两个equals
方法,以达到我们想要的目的。
总结一下
好了,关于编写equals
方法,你学会了吗?
大致流程总共就这几步:
先判定两个对象地址是否相等;
检测传入对象是否为null
-
判定来年各个对象是否是同一个类
- 如果是每个子类都需要重新定义自己的比较功能 使用
getClass
比较; - 如果子类可以交由父类直接进行判定,则使用
instanceof
比较;
- 如果是每个子类都需要重新定义自己的比较功能 使用
进行类型转换
使用
==
比较基本数据类型,使用Objects.equals
来比较两个对象的引用类型的属性;
如果觉得不错,请各位记得点赞、点个在看哦!!!
版权声明:本站所有文章除特别声明外,均采用 CC BY-NC-ND 4.0
转载请注明来自 kengwanglaoxue
当前文章作者名:kengwanglaoxue
当前文章标题:你真的会写equals方法吗?
当前文章原创地址:https://997coder.com/equals.html