我说对象相等,HashCode一定相等,面试官笑了...

本文已收录于:https://github.com/danmuking/all-in-one

哈喽,大家哈,我是 DanMu。今天在刷博客的时候刷到一个挺有意思的面试题:“ 两个对象相等,Hashcode一定相等,那如果两个对象不相等,**HashCode**有没有可能相等?”

Java中想要比较两个对象是否相同,主要有两种方式:

  1. 重写equals方法,通过调用equals方法进行比较
  2. 直接使用==进行比较

这也是老八股了,直接开始吟唱

equals()方法

equals()的作用是用来判断两个对象是否相等equals()定义在JDKObject类中。默认的equals()通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。源码如下:

publicbooleanequals(Objectobj){
return(this==obj);
}

既然Object中定义了equals()方法,这就意味着所有的Java类都实现了equals()方法,所有的类都可以通过**equals()**去比较两个对象是否相等。并且默认的equals方法等价于==,因此,我们通常需要重写比较两个对象的逻辑:若两个对象的内容相等,则返回true;否则,返回fasle。都是基础的知识了这里简单提一下,知道的朋友可以跳过:

不重写equals()方法

如果不重写equals(),那实际上,调用的Objectequals()方法,即调用的(p1==p2)。它是比较“p1p2是否是同一个对象”。由于p1p2都是new出来的对象,由独立的空间地址。因此,返回结果是false

public class EqualsTest1{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        // false
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }
    }
}

重写equals()方法

在实际代码中,我们通常都会采用这种方法,比如下面这段代码,我们在中重写了Personequals()函数:当两个Person对象的nameage都相等,则返回true

public class EqualsTest2{

    public static void main(String[] args) {
        // 新建2个相同内容的Person对象,
        // 再用equals比较它们是否相等
        Person p1 = new Person("eee", 100);
        Person p2 = new Person("eee", 100);
        // true
        System.out.printf("%s\n", p1.equals(p2));
    }

    /**
     * @desc Person类。
     */
    private static class Person {
        int age;
        String name;

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

        public String toString() {
            return name + " - " +age;
        }

        /**
         * @desc 覆盖equals方法
         */
        @Override
        public boolean equals(Object obj){
            if(obj == null){
                return false;
            }

            //如果是同一个对象返回true,反之返回false
            if(this == obj){
                return true;
            }

            //判断是否类型相同
            if(this.getClass() != obj.getClass()){
                return false;
            }

            Person person = (Person)obj;
            return name.equals(person.name) && age==person.age;
        }
    }
}

equals()==的区别是什么?

简单小结一下:
== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不试同一个对象。
equals() : 它的作用也是判断两个对象是否相等。但它有两种使用情况:

  1. 没有重写equals()方法。则通过equals()比较该类的两个对象时,等价于通过==比较这两个对象。
  2. 重写了equals()方法。一般,我们都重写equals()方法来判断两个对象的内容是否相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

hashCode()方法

上面分析了equals()==,似乎通过这两个方法已经可以实现判断对象是否相等的任务了,那么,为什么还需要**hashCode()**方法呢?它的作用是什么呢?
hashCode()的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。hashCode() 同样定义在JDKObjest中,这就意味着Java中的任何类都包含有hashCode()函数。
但是!虽然每个Java类都包含hashCode()函数。但是!仅仅当创建并某个“散列表”时,该类的**hashCode()**才会被使用,其它情况下,hashCode()将不会被使用。Java中常见的散列表主要是Map接口的实现类,比较常见的如HashMapHashtableHashSet等等。
我们都知道,散列表存储的是键值对(key-value),它的特点是:能根据“key”快速的检索出对应的“value”。这其中就用到了**hashCode()**

散列表的数据结构一般是通过数组+链表实现的。当我们要获取散列表中的某个“value”时,实际上是要获取数组中的某个位置的元素。而数组的位置,就是通过“key”来获取的;更进一步说,数组的位置,是通过“key”对应的**hashCode()**计算得到的。
我们想要从散列表中检索某个元素时,会经过两个步骤

  1. 根据hashCode()计算出元素在数组中的哪个下标位置
  2. 遍历对应位置的链表,调用equals()方法判断两个对象是否相等

因此,hashCode()的主要作用,就是用于计算元素在散列表中的位置。

hashCode() equals() 的关系

那么接下来我们来总结一下hashCode() equals()的关系

  1. 使用散列表时
    1. 如果两个对象相等,那么它们的hashCode()值一定相同。
    2. 如果两个对象hashCode()相等,它们并不一定相等。 因为在散列表中,hashCode()相等,只能说明两个对象被存放在数组的同一个下标的链表中,但是并不能说明这两个对象的内容相等。
  2. 不使用散列表时hashCode()根本不会被调用,所以两者之间实际上毫无关系。

在阿里巴巴开发手册-华山版中有一处强制规定:
**【强制】**关于hashCode和equals的处理,遵循如下规则:
1)只要覆写equals,就必须覆写hashCode
2)因为Set存储的是不重复的对象,依据hashCodeequals进行判断,所以Set存储的对象必须覆写这两个方法。
3)如果自定义对象作为Map的键,那么必须覆写hashCodeequals
说明:String已覆写hashCodeequals方法,所以我们可以愉快地使用String对象作为key来使用。

两个对象不相等,HashCode有没有可能相等?

在深入了解了**equals()****hashCode()**的基本原理后,我们可以来探讨一个有趣的问题:两个对象不相等,**HashCode**有没有可能相等?
前面已经提到了,hashcodes()定义在Object中,并且允许子类重载,最终的返回值是一个int类型,那么我们当然可以通过重写**hashCode()**来返回相同的**HashCode**,比如这个例子

// 鹿
class Deer{
    double age;
    Deer(double age){
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        Deer itemOne = (Deer) o;
        return Objects.equals(age, itemOne.age);
    }

    @Override
    public int hashCode() {
        return 1;
    }
}

// 马
class Horse {
    double age;
    Horse(double age){
        this.age = age;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        Deer itemOne = (Deer) o;
        return Objects.equals(age, itemOne.age);
    }

    @Override
    public int hashCode() {
        return 1;
    }
}

上面的例子分别定义了一个Deer类和一个Horse类,但是由于重写了他们的hashCode()方法,因此它们的hashCode都是1,在这种情况下,如果使用散列表将会发生一些有趣的事情:

 public static void main(String[] args) {
    Deer deer = new Deer(2.5);
    Horse horse = new Horse(2.5);
    Set<Object> set = new HashSet<>();
    set.add(deer);
     // true
    System.out.println(set.contains(horse));
 }

在这种情况下,明明我们只向set中添加了deer,但是在判断set.contains(horse)的时候,set出现了误判。原因是,在set进行contains判断的时候,首先会根据hashCode()找到对应的数组下标,由于两个类的hashCode()相同,所以将会索引到同一个位置,然后遍历链表元素,由于当前只存在一个元素deer,因此就会调用equals()方法比较两个元素是否相同,正好,两个对象有具有相同的属性,因此set就认为这是两个相同的对象。
想要避免这个错误其实也非常简单,虽然这两个对象具有相同的属性,但是这两个对象属于不同的类,因此,只需要在**equals()**中额外判断是否是同一个类即可:

class Deer{
    ...
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Deer)) return false;
        Deer itemOne = (Deer) o;
        return Objects.equals(age, itemOne.age);
    }
    ...
}

class Horse {
    ...
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Horse)) return false;
        Deer itemOne = (Deer) o;
        return Objects.equals(age, itemOne.age);
    }
    ...
}

但是如果正巧,两个对象有正好存在继承关系,那上面这种防御措施仍然会失效,因此,最好最好的方法,就是别搞事情,乖乖的使用自动生成hashcode,这样可以极大程度的避免出现这种意外情况。

点关注,不迷路

好了,以上就是这篇文章的全部内容了,如果你能看到这里,非常感谢你的支持!
如果你觉得这篇文章写的还不错, 求点赞👍 求关注❤️ 求分享👥 对暖男我来说真的 非常有用!!!
白嫖不好,创作不易,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !

最后推荐我的IM项目DiTinghttps://github.com/danmuking/DiTing-Go),致力于成为一个初学者友好、易于上手的 IM 解决方案,希望能给你的学习、面试带来一点帮助,如果人才你喜欢,给个Star⭐叭!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值