关于Java中重写equals方法时一定要重写hashCode方法

主要转载自:
https://fangjian0423.github.io/2016/03/12/java-Object-method/

hashCode方法

原型:public int hashCode()
hashCode方法是一个native方法。
该方法返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。支持此方法是为了提高哈希表(例如java. uti1. Hashtable提供的哈希表)的性能.

hashCode的常规协定是:

  • 在Java程序执行期间,在一个对象没有被改变的前提下,无论这个对象被调用多少次,hashCode方法都会返回相同的整数值。对象的哈希码没有必要在不同的程序中保持相同的值。
  • 如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。
  • 如果根据 equals方法,两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,为不相等的对象生成不同hashCode值可以提高哈希表的性能。

通常情况下,不同的对象产生的哈希码是不同的。默认情况下,对象的哈希码是通过将该对象的内部地址转换成一个整数来实现的。

hashCode分析

结论:

java6、7默认是返回随机数
java8默认是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia’s xorshift scheme随机数算法得到的一个随机数

hashCode在哈希表HashMap中的应用:

// Student类,只重写了hashCode方法
public static class Student {

    private String name;
    private int age;

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

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

Map<Student, String> map = new HashMap<Student, String>();
Student stu1 = new Student("fo", 11);
Student stu2 = new Student("fo", 22);
map.put(stu1, "fo");
map.put(stu2, "fo");

上面这段代码中,map中有2个元素stu1和stu2。但是这2个元素是在哈希表中的同一个数组项中的位置,(hash冲突问题),也就是在同一串链表中。 但是为什么stu1和stu2的hashCode相同,但是两条元素都插到map里了,这是因为map判断重复数据的条件是 两个对象的哈希码相同并且(两个对象是同一个对象或者两个对象相等[equals为true])。 所以再给Student重写equals方法,并且只比较name的话,这样map就只有1个元素了。

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    Student student = (Student) o;
    return this.name.equals(student.name);
}

这个例子直接说明了hashCode中通用约定的第三点:

第三点:如果根据equals方法,得到两个对象不相等,那么这2个对象的hashCode值不需要必须不相同。但是,不相等的对象的hashCode值不同的话可以提高哈希表的性能。 –> 上面例子一开始没有重写equals方法,导致两个对象不相等,但是这两个对象的hashCode值一样,所以导致这两个对象在同一串链表中,读取表的时候会影响性能。

当然,还有第三种情况,那就是equals方法相等,但是hashCode的值不相等。

这种情况也就是违反了通用约定的第二点:

第二点:如果2个对象使用equals方法进行比较并且相同的话,那么这2个对象的hashCode方法的值也必须相等。 违反这一点产生的后果就是如果一个stu1实例是Student(“fo”, 11),stu2实例是Student(“fo”, 11),那么这2个实例是相等的,但是他们的hashCode不一样,这样是导致哈希表中都会存入stu1实例和stu2实例,但是实际情况下,stu1和stu2是重复数据,只允许存在一条数据在哈希表中。所以这一点是非常重点的,再强调一下:如果2个对象的equals方法相等,那么他们的hashCode值也必须相等,反之,如果2个对象hashCode值相等,但是equals不相等,这样会影响性能,所以还是建议2个方法都一起重写。

Term类需要作为Map的key,假如Term类重写了Equals方法而没有重写hashCode方法,因为Java.util.HashMap的中put方法的具体实现,先计算key的hash值,从table数组中取出对应节点,如果节点不存在则添加一个节点;如果存在则更新value,返回旧value。如果没有重写hashCode,不同Term的实例对象就算content相同,它们的hashCOde方法返回值也不同,那么Map就会把这两个实例Term都添加为Map的Key。显然是不符合我们的需求的。

1、hashCode方法的作用

在Java中也一样,hashCode方法的主要作用是为了配合基于散列的集合一起正常运行,这样的散列集合包括HashSet、HashMap以及HashTable。

为什么这么说呢?考虑一种情况,当向集合中插入对象时,如何判别在集合中是否已经存在该对象了?

也许大多数人都会想到调用equals方法来逐个进行比较,这个方法确实可行。但是如果集合中已经存在一万条数据或者更多的数据,如果采用equals方法去逐一比较,效率必然是一个问题。

此时hashCode方法的作用就体现出来了,当集合要添加新的对象时,先调用这个对象的hashCode方法,得到对应的hashcode值,实际上在HashMap的具体实现中会用一个table保存已经存进去的对象的hashcode值,如果table中没有该hashcode值,它就可以直接存进去,不用再进行任何比较了;如果存在该hashcode值, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址,所以这里存在一个冲突解决的问题,这样一来实际调用equals方法的次数就大大降低了,说通俗一点:Java中的hashCode方法就是根据一定的规则将与对象相关的信息(比如对象的存储地址,对象的字段等)映射成一个数值,这个数值称作为散列值。

java.util.HashMap的中put方法的具体实现,先计算key的hash值,从table数组中取出对应节点,如果节点不存在则添加一个节点;如果存在则更新value,返回旧value。

下面这个更加详细:
https://blog.csdn.net/csdn_baotai/article/details/82224127
在这里插入图片描述
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;

②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;

③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;

④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;

⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;

⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值