Java深入理解equals

在Java中Object类中的equals方法用于检测一个对象是否等于另一个对象。注意:这里的等于指的是两个对象的引用是否相等,即所谓的地址,这看起来好像没啥问题;不就是两个对象引用相等,则两对象相等!

但在实际开发中,往往是检测两对象的状态属性

例如:如果两个学生的姓名,学号,年龄都一样,那就相等**(注意:实际上我们通常会结合数据库进行开发,真正的比较应该是主键(如id)),**因此我们需要重写equals方法,请看样例:

public  class Student{
    private int id;
    private int age;
    private String name;
    
    //构造方法
    public Student (int id,int age,String name){
        代码省略
    }
    
    //省略了getter和setter
    
    
    public static void main(String[] args){
        var student1 = new Student(1,18,"luo");
        var student2 = new Student(1,18,"luo");
        student.equals(student3);       //retuen true
        //如果单纯的比比引用那这两个学生肯定不同
    }
    //重写的equals方法
    public boolean equals(Object object){
       if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (this.getClass() != obj.getClass())
            return false;

        var other_student = (Student) obj;
        return id = other_student.getId() &&
               age = other_stuedet.getAge()&&
            	Objects.equals(name,other_student.getName());
    }
}

如何重写equals?

引入

如果隐式参数和显示参数不属于同一个类,equals方法将如何处理?在上面例子中,只要发现类不匹配,就返回false。但是很多人喜欢这样:

if (!(obj instanceof Stuent))
            return false;

这样写就允许obj属于一个子类但这中方法会招致一些麻烦(稍后会有解释),因为equals的一些规范

equals方法规范
  • 自反性:见名知义
x.equals(x)    return true
  • 对称性:
:就好比 x.equals(y) ,y.equals(y) 的结果相同
  • 传递性:
y.equals(z)返回true,x.equals(z)返回true 那么x.equals(y) 也应该返回true
  • 一致性:如果x和y引用的对象没有发生变化,反复调用x.equals(y)应该的返回相同的结果。

这些规则都很合理,就对称性来说,当参数不属于同一类时会有一些微妙的结果,例如:

student.equals(person);
//student extends person

解释:如果这两个对象都有相同姓名,年龄,id。如果在Student.equals中调用instance进行检测:it will return true; 但是如果反过来调用:it will return false;

这样的情况,让人猛然觉得instance 是多么不方便!这就好比 x等于y,y却不等于x 这部扯淡吗!

instanceof 和getClass()

既然有人用instanceof ,存在即合理;上面的勿用现象其实实际情况都不理解: instanceofgetclass()其实对应两种情况

  • 如果子类可以有自己的相等性概念,则要按照对称性使用getClass()
  • 如果子类有父类决定 相等性概念,那么就用instanceof检测,这样可以在不同的子类进行相等性比较

最后给出一个编写完美的equals方法的建议

  1. 检测this与otherObject 是否相等;(这条语句是一个优化,实际上,这是经常采用的一种形式。因为一开始检查引用身份要比逐个检查字段开销小)
  2. 检测otherObject 是否为null,如果为null,返回false.这项检测是非常有必要的
  3. 比较this与object类,如果子类有自己的相等性概念就用getclass检测
    如果由父类决定相等性概念,那么就用instanceof检测
  4. 将显示参数Object强制转换相对应的类型变量:
  5. 比较各个成员·字段

具体如下:由于学生类具有自己的相等性概念 所以第3步采用 getClass()

public boolean equals(Object obj){
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (this.getClass() != obj.getClass())
            return false;

        var other_student = (Student) obj;
        return id = other_student.getId() &&
               age = other_stuedet.getAge()&&
            	Objects.equals(name,other_student.getName());

我曾经犯过的傻逼错误

public boolean equals(Stuedent obj){
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (this.getClass() != obj.getClass())
            return false;

        var other_student = (Student) obj;
        return id = other_student.getId() &&
               age = other_stuedet.getAge()&&
            	Objects.equals(name,other_student.getName());

大家应该知道错误在哪吧? 各位老哥就引以为戒吧! 为此,我们以后再重写方法时,最好添上一个@Override