Java-equals()&hashCode()区别于联系

Scroll Down

equals() 和 hashCode()区别和联系

我们经常听到重写equals和hashCode,但是我们为什么要去重写呢?如果我们不重写会怎么样呢?又或者我们只重写其中一个又会怎么样呢?带着这样的问题我们来看下equals()和hashCode()具体的区别和联系是什么?


在说明之前我们先了解下equals和hashCode

  • 在线性数据结构中使用equals来比较两个对象,线性结构中比较对象不涉及hashCode;
  • 在散列结构中,会使用hashCode会参与和比较散列结构中的对象

关于equals和hashCode的一些总结

  1. 若重写了equals(Object obj)方法,则有必要重写hashCode()方法。
  2. 若两个对象equals(Object obj)返回true,则hashCode()有必要也返回相同的int数。
  3. 若两个对象equals(Object obj)返回false,则hashCode()不一定返回不同的int数。
  4. 若两个对象hashCode()返回相同int数,则equals(Object obj)不一定返回true。
  5. 若两个对象hashCode()返回不同int数,则equals(Object obj)一定返回false。
  6. 同一对象在执行期间若已经存储在散列集合中,则不能修改影响hashCode值的相关信息,否则会导致内存泄露问题。

散列结构中存放元素的顺序

  1. add/put 对象到散列集合中
  2. 判断集合中是否存在任意一个对象hashCode值是否相等,相等走步骤3,不相等走步骤4
  3. hashCode相等,那么继续判断是否有任一对象equals相等,相等走步骤5,不想等走步骤4
  4. 放入该对象
  5. 舍弃该对象

equals()

  • 默认情况下equals()使用的Object的equals方法,通过查看源码可以知道是直接用两个比较对象的内存地址去比较,从这里我们就可以知道,如果我们不重写equals方法,那么当我们比较对象的时候其实equals比较的是这两个对象的内存地址是不是一样,==在这里特别注意String,因为String重写了equals,比较的是字符串内容==
  • 因此在有些特定场景下当我们判断自己的业务对象是不是一样的时候,可以通过重写equals来判断,例如对象中的每个属性值都一样,那么我们就认定这两个对象在业务层面是相同的,即使这两个对象在内存中不是同一个地址,那么我们可以通过重写equals方法来实现,可以参考示例代码
##java源码
public boolean equals(Object obj) {
        return (this == obj);
    }
##String equals()源码
 public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

hashCode()

在了解了散列结构保存数据的顺序后,那么当我们重写了equals方法后,在散列结构中我们最好也重写hashCode,否则有可能出现在业务层面相同的对象,但是在散列集合中,判定这些相同对象为不同对象,造成散列集合中出现不合适的数据

示例演示

package com.spring.springsecurity.springsecurityboot;

import java.util.HashSet;
import java.util.Set;

public class Test {

    /**
     *基于业务需要我们重写了equals方法, 通过例子我们可以看到,
     * 1.==比较的是对象的内存地址,所以我们new的对象在==判断时都返回是false;
     * 2.不管是Stu还是Tea对象,因为我们基于业务需要重写了equals方法, 所以在equals方法中即使我们都是new的不同的对象,但最终返回的判断结果也是true,
     * 3.但是我们可以看到如果我们不重写hashcode,在线性结构中没有影响,但是在非线性结构中(hash结构),我们可以看到没有重写hashcode,
     * 会使用父类Object对象的hashcode,即使在业务层面equals的对象,也会在hash结构中当作不一样的去处理,这可能会引起我们业务上的问题;但是如果我们重写了hashcode,那么即使在hash结构中符合equals的对象也会保持和业务一致。
     * */
    public static void main(String[] args) {
        Set<Stu> set = new HashSet<Stu>();
        Stu s1 = new Stu("troy1",1);
        Stu s2 = new Stu("troy2",2);
        Stu s3 = new Stu("troy1",1);
        Stu s4 = new Stu("troy1",1);
        set.add(s1);set.add(s2);set.add(s3);set.add(s4);
        System.out.println(s1 == s2);
        System.out.println(s1 == s3);
        System.out.println(s1 == s4);

        System.out.println();
        System.out.println();

        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s3));
        System.out.println(s1.equals(s4));

        System.out.println();
        System.out.println();

        System.out.println(s1.hashCode() + "~~~" +s2.hashCode() + "~~~" +s3.hashCode() + "~~~" +s4.hashCode());

        System.out.println();
        System.out.println();

        set.forEach(f->{
            System.out.println(f.hashCode());
        });

        System.out.println();
        System.out.println();

        System.out.println("=====================Tea=======================");
        Set<Tea> set1 = new HashSet<Tea>();
        Tea t1 = new Tea("Tea1",1);
        Tea t2 = new Tea("Tea2",2);
        Tea t3 = new Tea("Tea1",1);
        Tea t4 = new Tea("Tea1",4);
        Tea t5 = new Tea("Tea1",1);
        Tea t6 = new Tea("Tea1",1);
        set1.add(t1);set1.add(t2);set1.add(t3);set1.add(t4);set1.add(t5);set1.add(t6);
        System.out.println(t1 == t2);
        System.out.println(t1 == t3);
        System.out.println(t1 == t4);
        System.out.println(t1 == t5);
        System.out.println(t1 == t6);

        System.out.println();
        System.out.println();

        System.out.println(t1.equals(t2));
        System.out.println(t1.equals(t3));
        System.out.println(t1.equals(t4));
        System.out.println(t1.equals(t5));
        System.out.println(t1.equals(t6));

        System.out.println();
        System.out.println();

        System.out.println(t1.hashCode() + "~~~" +t2.hashCode() + "~~~" +t3.hashCode() + "~~~" +t4.hashCode()+ "~~~" +t5.hashCode() + "~~~" +t6.hashCode());

        System.out.println();
        System.out.println();

        set1.forEach(f->{
            System.out.println(f.hashCode());
        });
    }
}

class Stu{
    private String name;
    private int age;

    public Stu(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if(null == obj || !(obj instanceof Stu)) {
            return false;
        }

        if(!this.name.equals(((Stu)obj).name)) {
            return false;
        }

        if(!(this.age == ((Stu)obj).age)) {
            return false;
        }
        return true;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

class Tea{
    private String name;
    private int age;

    public Tea(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if(null == obj || !(obj instanceof Tea)) {
            return false;
        }

        if(!this.name.equals(((Tea)obj).name)) {
            return false;
        }

        if(!(this.age == ((Tea)obj).age)) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        return name.hashCode() + age;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}

输出结果:

false
false
false


false
true
true


1323165413~~~1880587981~~~511754216~~~1721931908


1721931908
1323165413
511754216
1880587981


=====================Tea=======================
false
false
false
false
false


false
true
false
true
true


2602562~~~2602564~~~2602562~~~2602565~~~2602562~~~2602562


2602565
2602564
2602562

总结

  • “====”比较的是对象的内存地址,所以我们new的对象在判断时都返回是false;
  • 不管是Stu还是Tea对象,因为我们基于业务需要重写了equals方法,所以在equals方法中即使我们都是new的不同的对象,但最终返回的判断结果也是true;
  • 但是我们可以看到如果我们不重写hashcode,在线性结构中没有影响,但是在非线性结构中(hash结构),我们可以看到没有重写hashcode,会使用父类Object对象的hashcode,即使在业务层面equals的对象,也会在hash结构中当作不一样的去处理,这可能会引起我们业务上的问题;但是如果我们重写了hashcode,那么即使在hash结构中符合equals的对象也会保持和业务一致。