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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,311评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,339评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,671评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,252评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,253评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,031评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,340评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,973评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,466评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,937评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,039评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,701评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,254评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,259评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,485评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,497评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,786评论 2 345

推荐阅读更多精彩内容