对于java重写equals和hashCode方法的基础理解

目录

为什么重写equals方法

基本数据类型的比较

首先得知道,对于基本数据类型的比较我们直接用双等符合(==)就能直接判断两个值是否相等。

  • 代码理解
        int a = 100;
        int b = 200;
        int c = 100;
        System.out.println(a); //100
        System.out.println(b); //200
        System.out.println(c); //100
        System.out.println(a == b);  //false
        System.out.println(a == c);  //true

从中可以得到结论:
基本数据类型直接存储的是值,没有引用一说,即a与b、a与c的比较是直接用它们的值进行比较的,所以可以直接用 == 比较。

引用数据类型的比较

但java中除了基本数据类型,还有引用数据类型。引用数据类型包括常用的String和我们自定义的类等等。我们重写equals方法的目标也正是这些自定义的类。

  • 难道自定义类就不可以直接用 == 比较了吗?答案是不行的
    直接看代码(我们自定义一个Student类,给它name和age两个属性,它的set、get和有参构造方法就不粘出来了)
class Student{
        private String name;
        private Integer age;
        }

我们先对没有重写equals()的Student类进行测试:

        Student student1 = new Student("zhangsan", 20);
        Student student2 = new Student("lisi", 20);
        Student student3 = new Student("lisi", 20);
        System.out.println(student1); //com.nbtech.dorm.Test$Student@490d6c15
        System.out.println(student2); //com.nbtech.dorm.Test$Student@7d4793a8
        System.out.println(student3); //com.nbtech.dorm.Test$Student@449b2d27

得出结论:
直接输出这些变量,得到的不是name或age,也不是name和age的任何一种组合形式,而是一条奇奇怪怪的东西,并且数据相同时这串东西也是不一样的。
可以大致分析这串东西知道,@前面的是该对象的所属类及包的位置,而@后面的则是地址,我们不需要知道这串东西怎么来的,只需要知道,引用数据类型的变量存的是一个地址,而不是具体的值,变量会通过这个地址在堆内存中找到指向的对象。

因此,直接用 == 不能比较两个对象是否相等一致,因为实际上只是在比较两条内存地址而已。所以,比较对象必须用equals方法(这里不要把equals()特殊化,实际上a方法b方法都可以,只有方法的内容是能比较的就可以)。

  • 重写equals()的理由
    理由都是源自于需求的,既然我们自定义了一些类,那么在调用的时候不免会出现对象与对象的比较,我们的预期是当对象间里面的属性值都相同时判定为true,或认为name值相同就判定对象相同也是可以的,这个看需求。

  • 重写equals()
    其实 equals方法重写的格式相对统一,像Idea是有统一的格式直接生成的,方法:在类中按ALT + Insert就可以看到。
    对Student类直接生成重写的equals方法和对应解析如下:

 @Override
 public boolean equals(Object o) {
     /*当前对象和传入对象的内存地址相同是直接相同返回true*/
     if (this == o) return true;
     /*当传入对象为null或不是同一个引用数据类型直接返回false*/
     if (o == null || getClass() != o.getClass()) return false;
     /*让对象之间的属性值进行一一比较,全相同时返回true,否则返回false*/
     Student student = (Student) o;
     return Objects.equals(name, student.name) &&
            Objects.equals(age, student.age);
  }

直接生成的equals方法的适用场景是所有属性值相同时才相等,这也适用于大部分场景。当然,需求不一样可以进行对应修改。
接下来,我们就可以用equals()进行对象间的比较了

      Student student1 = new Student("lisi", 20);
      Student student2 = new Student("zhangsan", 20);
      Student student3 = new Student("lisi", 20);
      System.out.println(student1.equals(student2)); //false
      System.out.println(student1.equals(student3)); //true

符合预期结果。

为什么重写hashCode方法

刚开始可以有些人会疑惑:既然重写了equals方法,已经可以让我在对对象进行比较的时候得到预期结果了,重写hashCode方法还有什么必要呢?
确实,如果在你的项目中对象只需要在 i f 语句中使用,确实不需要重写hashCode方法,重写equals方法就能满足你的要求。(实际上这也得重写hashCode,原因是对象存储的内存消耗问题)
但有些场景是equals不能满足的,比如:当需要使用HashMapHashSet的时候。

  • HashMap
    首先得知道HashMap是以键值对的形式存储数据的,且键是唯一的。那么,当key是Student类型时,单单重写equals方法能符合这些规则吗?看代码:
        //只重写equals(),不重写hashCode()
        Student student = new Student("tom", 20);
        Student student1 = new Student("tom", 20);
        Map<Student,String> map = new HashMap();
        map.put(student,"001");
        map.put(student1,"002");
        System.out.println(map.get(student)); //001
        System.out.println(map.get(student1)); //002
        System.out.println(map.size()); //2

我们可以看到,在同一个HashMap中成功放入了两个key值相同的student(当然,这是我们认为的而已,因为我们的定义是属性值相同就相同),这是不符合HashMap本身的规则的。
那为什么key规则上是唯一的又能同时插入呢?因为,HashMap底层是通过比较hashCode(哈希值)来插入的,即key值唯一值的是key的哈希值唯一。
看看它们的哈希值:

      System.out.println(student.hashCode()); //1225616405
      System.out.println(student1.hashCode()); //2101842856

不管获取几次哈希值都不会改变,算法是通过对象的属性值计算的一个固定值,也可以自己写一个算法。

重写hashCode()

        Student student = new Student("tom", 20);
        Student student1 = new Student("tom", 20);
        Map<Student,String> map = new HashMap();
        map.put(student,"001");
        map.put(student1,"002");
        System.out.println(map.get(student)); //002
        System.out.println(map.get(student1)); //002
        System.out.println(map.size()); //1

通过size()可以看到,这个HashMap只有1个元素,进一步通过key值get出它们对应的值,发现都是002,说明HashMap确立了对于Student对象的唯一性,后面插入的student1覆盖了前面的student,因此值为002。
再来看看它们的哈希值:

        System.out.println(student.hashCode()); //3566787
        System.out.println(student1.hashCode()); //3566787

确实都一样!
hashCode()是如何重写的
同样这里也是用idea直接生成的。生成的代码如下:

        @Override
        public int hashCode() {
            return Objects.hash(name, age);
        }

这里是通过Objects调用hash方法并传入当前类所有属性值获得的哈希值,不能看明白具体的实现。
继续看看hash()怎么实现的,如下:

    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }

Object… values这是一个可变参数,传入的值会装进values这个数组中。
这里也不能看到具体的实现,继续一探究竟,如下:

public static int hashCode(Object a[]) {
        //当数组为空时,直接返回0
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            //这个result是最终计算的哈希值,以31 * 1为基础值再加上数组中(即对象属性值)
            //不为空的哈希值
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

好了,这是Arrays的一个计算哈希值的算法,是不是很简单,完全可以自己写一个。
哈希值其他的用处大家可以自己看看,这里不多说啦!本文为基础讲解,可以以此再深入研究。另外,文章可能存在不严谨或错误,望指出。

上一篇:真正搞懂hashCode和hash算法


下一篇:Android Studio && GitHub 团队多人一起开发