你真的会写equals方法吗?

2 篇文章 0 订阅
1 篇文章 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.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值