前言

在使用集合存储非Java基本类型与String的对象涉及到排序或者其他操作时,我们总是会同时重写hashCodeequals方法,但是我一直不明白其中的具体原因,今天扒一下。

: String类型虽然也为引用类型,但是Java中已经帮我们重写完毕hashCodeequals方法。

示例代码结构

结构
在这里插入图片描述

Student类

注:为了便于查看控制台信息,所以先重写了toString方法打印字段信息。

package bean;

public class Student {
    private String stuNumber;
    private String name;

    public Student(){

    }
    public Student(String stuNumber, String name){
        this.stuNumber=stuNumber;
        this.name=name;
    }

    public String getStuNumber() {
        return stuNumber;
    }

    public void setStuNumber(String stuNumber) {
        this.stuNumber = stuNumber;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "stuNumber:"+stuNumber+" name:"+name+"\n";
    }
}

main方法调用类

public class Main {
    public static void main(String[] args) {
        //...
    }
}

情景演示

情景一:不重写equals与hashCode

普通情况下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");
        //运算符'==' 仅仅是比较了两个变量对象的地址(java运行时数据区的堆地址)
        System.out.println(obj_1 == obj_2);
        /**
         * 1. 默认equals方法依旧是比较 java运行时数据区的堆地址 的异同
         * public boolean equals(Object obj) {
         *         return (this == obj);
         *     }
         * 2.在没有没有重写父类的情况下,默认hashCode方法 每次运行的结果都是不同整数,称为哈希值,没有特别意义
         */
        System.out.println(obj_1.equals(obj_2));

        /**
         * 扩展:对于方法中的局部变量的引用时存放在java运行时数据区的栈中,对于实例变量则是存放在java运行时数据区的堆中。
         */
    }

输出

---------------------
obj_1 hashCode:460141958
obj_2 hashCode:1163157884
---------------------
false
false

Process finished with exit code 0

在哈希存储结构下使用

测试

 public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");
        //不可以重复存在的HashSet集合
        HashSet<Student> students = new HashSet<>();
        /**
         * 1. 首先判断哈希值是否重复(重复性校验),
         * 2,如果不同则直接添加,不再调用equals
         * 3. 如果相同则继续调用equals判断
         * 4. 判断equals相同则不再添加该元素,否则添加该元素
         */
        //这里因为没有重写hashCode与equals,所以无法判断是否重复
        students.add(obj_1);
        students.add(obj_2);

        //遍历
        for (Student temp:students) {
            System.out.printf(temp.toString());
        }
    }

输出

---------------------
obj_1 hashCode:460141958
obj_2 hashCode:1163157884
---------------------
stuNumber:2021 name:小黑
stuNumber:2021 name:小黑

Process finished with exit code 0

情景二:仅重写equals

重写代码

	@Override
    public boolean equals(Object o) {
        //如果 java运行时数据区的堆地址相等 则表示同一对象 肯定相等
        if (this == o) return true;
        //如果为空 或者 不是一个类型 则肯定不相等
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        //判断属性值是否相等,这里采用了工具类Objects的方法判断
        return Objects.equals(stuNumber, student.stuNumber) && Objects.equals(name, student.name);
    }

普通情况下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");
        //运算符'==' 仅仅是比较了两个变量对象的地址(java运行时数据区的堆地址)
        //由于这是两个实例对象,在堆上开辟了两个不同的空间,所以地址一定不同
        System.out.println(obj_1 == obj_2);
        //这里便可以判断值了
        System.out.println(obj_1.equals(obj_2));
    }

输出

---------------------
obj_1 hashCode:460141958
obj_2 hashCode:1163157884
---------------------
false
true

Process finished with exit code 0

在哈希存储结构下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");
        //不可以重复存在的HashSet集合
        HashSet<Student> students = new HashSet<>();
        /**
         * 1. 首先判断哈希值是否重复(重复性校验),
         * 2,如果不同则直接添加,不再调用equals
         * 3. 如果相同则继续调用equals判断
         * 4. 判断equals相同则不再添加该元素,否则添加该元素
         * 总结:只有在哈希值相同的情况下才会调用equals具体判断属性值
         */
        //这里虽然重写了equals方法,但是由于采用哈希算法实现的集合首先会判断哈希值重复
        //由于没有重写hashCode方法,导致这两个对象即便属性值都相等的情况下,哈希值也是不一样的
        //所以这里会直接放入,不再调用equals判断
        students.add(obj_1);
        students.add(obj_2);

        //遍历
        for (Student temp:students) {
            System.out.printf(temp.toString());
        }
    }

输出

---------------------
obj_1 hashCode:460141958
obj_2 hashCode:1163157884
---------------------
stuNumber:2021 name:小黑
stuNumber:2021 name:小黑

Process finished with exit code 0

情景三:仅重写hashCode

重写代码

@Override
    public int hashCode() {
        //使用工具类Objects的hash方法来将每个字段的哈希值按照算法组合返回 本实例的最终哈希值
        return Objects.hash(stuNumber, name);
    }

普通情况下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");

        //运算符'==' 仅仅是比较了两个变量对象的地址(java运行时数据区的堆地址)
        System.out.println(obj_1 == obj_2);
        /**
         * 默认equals方法依旧是比较 java运行时数据区的堆地址 的异同
         * public boolean equals(Object obj) {
         *         return (this == obj);
         *     }
         */
        System.out.println(obj_1.equals(obj_2));
    }

输出

---------------------
obj_1 hashCode:48427782
obj_2 hashCode:48427782
---------------------
false
false

Process finished with exit code 0

在哈希存储结构下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:"+obj_1.hashCode());
        System.out.println("obj_2 hashCode:"+obj_2.hashCode());
        System.out.println("---------------------");

        //不可以重复存在的HashSet集合
        HashSet<Student> students = new HashSet<>();
        /**
         * 1. 首先判断哈希值是否重复(重复性校验),
         * 2,如果不同则直接添加,不再调用equals
         * 3. 如果相同则继续调用equals判断
         * 4. 判断equals相同则不再添加该元素,否则添加该元素
         * 总结:只有在哈希值相同的情况下才会调用equals具体判断属性值
         */
        //这里由于重写了hashCode,两个属性相同的对象的哈希值根据判断是相同的(因为每个对象的哈希值是其属性字段的哈希值按照一定的算法组合而成)
        //所以在执行完哈希值重复性校验后,得到哈希值相同的结果,开始调用equals方法具体判断,然而我们没有重写equals方法,导致默认判断堆地址,所以依旧放入对象
        students.add(obj_1);
        students.add(obj_2);

        //遍历
        for (Student temp:students) {
            System.out.printf(temp.toString());
        }
    }

输出

---------------------
obj_1 hashCode:48427782
obj_2 hashCode:48427782
---------------------
stuNumber:2021 name:小黑
stuNumber:2021 name:小黑

Process finished with exit code 0

情景四:同时重写hashCode与equals

重写代码

    @Override
    public boolean equals(Object o) {
        //如果 java运行时数据区的堆地址相等 则表示同一对象 肯定相等
        if (this == o) return true;
        //如果为空 或者 不是一个类型 则肯定不相等
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        //判断属性值是否相等,这里采用了工具类Objects的方法判断
        return Objects.equals(stuNumber, student.stuNumber) && Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        //使用工具类Objects的hash方法来将每个字段的哈希值按照算法组合返回 本实例的最终哈希值
        return Objects.hash(stuNumber, name);
    }

普通情况下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:" + obj_1.hashCode());
        System.out.println("obj_2 hashCode:" + obj_2.hashCode());
        System.out.println("---------------------");

        //运算符'==' 仅仅是比较了两个变量对象的地址(java运行时数据区的堆地址)
        System.out.println(obj_1 == obj_2);

        System.out.println(obj_1.equals(obj_2));
    }

输出

---------------------
obj_1 hashCode:48427782
obj_2 hashCode:48427782
---------------------
false
true

Process finished with exit code 0

在哈希存储结构下使用

测试

public static void main(String[] args) {
        Student obj_1 = new Student("2021", "小黑");
        Student obj_2 = new Student("2021", "小黑");
        //哈希值
        System.out.println("---------------------");
        System.out.println("obj_1 hashCode:" + obj_1.hashCode());
        System.out.println("obj_2 hashCode:" + obj_2.hashCode());
        System.out.println("---------------------");

        //不可以重复存在的HashSet集合
        HashSet<Student> students = new HashSet<>();
        /**
         * 1. 首先判断哈希值是否重复(重复性校验),
         * 2,如果不同则直接添加,不再调用equals
         * 3. 如果相同则继续调用equals判断
         * 4. 判断equals相同则不再添加该元素,否则添加该元素
         * 总结:只有在哈希值相同的情况下才会调用equals具体判断属性值
         */
        students.add(obj_1);
        students.add(obj_2);

        //遍历
        for (Student temp:students) {
            System.out.printf(temp.toString());
        }
    }

输出

---------------------
obj_1 hashCode:48427782
obj_2 hashCode:48427782
---------------------
stuNumber:2021 name:小黑

Process finished with exit code 0

总结

  1. hashCode主要用于提升查询效率,来确定在散列结构中对象的存储地址;
    采取重写hashCode方法,先进行hashCode比较,如果不同,那么就没必要在进行equals的比较了,这样就大大减少了equals比较的次数,这对比需要比较的数量很大的效率提高是很明显的,一个很好的例子就是在集合中的使用。
  2. 重写equals()必须重写hashCode()二者参与计算的自身属性字段应该相同
  3. 哈希类型的存储结构,添加元素重复性校验的标准就是先取hashCode值,如果不相等直接放入,不必再判断equals(),如果哈希值相等再判断equals()

补充:String

测试

public static void main(String[] args) {
        String s_1 = new String("你好");
        String s_2 = new String("你好");
        String s_3 = "你好";
        String s_4 = "你好";
        //哈希值
        System.out.println("---------------------");
        System.out.println("s_1 hashCode:" + s_1.hashCode());
        System.out.println("s_2 hashCode:" + s_2.hashCode());
        System.out.println("s_3 hashCode:" + s_3.hashCode());
        System.out.println("s_4 hashCode:" + s_4.hashCode());
        System.out.println("---------------------");
        System.out.println(s_1.equals(s_2));
        System.out.println(s_1.equals(s_3));
        //不可以重复存在的HashSet集合
        HashSet<String> s = new HashSet<>();
        /**
         * 1. 首先判断哈希值是否重复(重复性校验),
         * 2,如果不同则直接添加,不再调用equals
         * 3. 如果相同则继续调用equals判断
         * 4. 判断equals相同则不再添加该元素,否则添加该元素
         * 总结:只有在哈希值相同的情况下才会调用equals具体判断属性值
         */
        s.add(s_1);
        s.add(s_2);
        s.add(s_3);
        s.add(s_4);

        //遍历
        for (String a:s) {
            System.out.printf(a);
        }
    }

输出

---------------------
s_1 hashCode:652829
s_2 hashCode:652829
s_3 hashCode:652829
s_4 hashCode:652829
---------------------
true
true
你好
Process finished with exit code 0

参考阅读:
为什么重写equals方法,还必须要重写hashcode方法

Q.E.D.


仰望星空,还需脚踏实地。