关山难越,谁悲失路之人;萍水相逢,尽是他乡之客。
百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程教程 > 技术文章 > 正文

你真的会写equals方法吗? 我输麻了~

guanshanw 2023-09-29 21:46 14 浏览 0 评论

本来要休息了,结果看到了一篇文章,读完之后大(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;
}

大家看的时候,因为可能比较长,所以看起来稍微有点不痛快,我把不重要的代码修改一下,然后看:

最核心的代码就在这里,左侧是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

这里原因就在与如果出现了父子类的情况呢?

啥?没理解?看代码!!!

我们只需要把红色框框的代码互相掉个个,然后测试即可。

你会发现我们通过下面的代码进行测试,但是得到的结果是不同的。

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.htm

相关推荐

七条简单命令让您玩转Git
七条简单命令让您玩转Git

凭借着出色的协作能力、快速部署效果与代码构建辅助作用,Git已经得到越来越多企业用户的青睐。除了用于开发商业及消费级应用之外,众多科学及政府机构也开始尝试使用这...

2023-10-07 12:14 guanshanw

基本完整的关于Git分支branch的操作
基本完整的关于Git分支branch的操作

Git使用背景项目中要用到dev或者其他分支开发完代码,需要将该分支合并到master的需求操作步骤下面以dev名称为lex为分支名为例来操作一遍客户端操作:...

2023-10-07 12:14 guanshanw

Git 进阶(合并与变基)
Git 进阶(合并与变基)

在Git中整合来自不同分支的修改主要有两种方法:合并(merge)以及变基(rebase)合并(merge)merge流程图merge的原理是找到这两个分...

2023-10-07 12:13 guanshanw

Git学习笔记 003 Git进阶功能 part5 合并(第一部分)

合并(merge)是很常用的操作。尤其是一个庞大的很多人参与开发的企业级应用。一般会设定一个主分支,和多个副分支。在副分支开发完成后,合并到主分支中。始终保持主分支是一个完整的,稳定的最新状态的分支。...

非标题党,三张图帮你理解git merge和git rebase的区别
非标题党,三张图帮你理解git merge和git rebase的区别

初始场景:基于正常的开发分支修改几个小bug,然后在合并到开发分支上。gitmergegitcheckoutfeaturegitmergeho...

2023-10-07 12:13 guanshanw

git 初次使用(01)
git 初次使用(01)

先从github上克隆代码下来:使用vscode克隆代码如下图,填写上github仓库地址:vscode有时候克隆代码速度比较慢,可以用命令行方式克隆gitc...

2023-10-07 12:12 guanshanw

Git 远程操作

4.Git远程操作命令说明gitremote远程版本库操作gitfetch从远程获取版本库gitpull下载远程代码并合并gitpush上传远程代码并合并4.1远程版本库操作gitre...

Git常用命令-总结
Git常用命令-总结

创建git用户$gitconfig--globaluser.name"YourName"$gitconfig--globaluser.em...

2023-10-07 12:12 guanshanw

git中删除从别人clone下来项目的git信息,并修改为自己的分支

如果你从别人的Git存储库中克隆了一个项目,并想要删除与该存储库相关的Git信息,并将其修改为你自己的分支,则可以执行以下步骤:使用gitclone命令克隆存储库:gitclone<u...

git系列-回滚和放弃本地修改

回滚历史提交就是reset的功能。这种情况是已经提交远程仓库,需要回滚到之前的提交。gitreset--hardcommitId//注:强制提交后,当前版本后面的提交版本将会删掉!gi...

GIT使用小技巧大全
GIT使用小技巧大全

在大型软件工程的开发过程中,版本控制是无法绕过去的;目前来说,最火的版本控制软件就是GIT了。早两年SVN比较火,不过被大神linus喷了几次后,就日落西山了,...

2023-10-07 12:11 guanshanw

git相关命令-上
git相关命令-上

这些命令都是看了文档后,个人觉得比较有用的一些,展示给大家。回到远程仓库的状态抛弃本地所有的修改,回到远程仓库的状态。gitfetch--all&...

2023-10-07 12:10 guanshanw

Git命令行接口:掌握Git的必备技能
Git命令行接口:掌握Git的必备技能

Git是一款强大的分布式版本控制工具,它支持命令行界面操作。熟练掌握Git命令行接口,是开发者使用Git的必备技能之一。在这篇文章中,我们将介绍Git命令行接口...

2023-10-07 12:10 guanshanw

Git命令详解
Git命令详解

相信各位小伙伴们应该都对git有一些了解,毕竟作为代码管理的神器,就算不是IT行业的小伙伴肯定也或多或少的听说过一些。今天就来和小伙伴们分享一下自己总结的常用命...

2023-10-07 12:10 guanshanw

工作7年收集到的git命令
工作7年收集到的git命令

概念git中的术语解释:仓库也叫版本库(repository)stage:暂存区,add后会存到暂存区,commit后提交到版本库git安装linux...

2023-10-07 12:10 guanshanw

取消回复欢迎 发表评论: