那些年追过的hashCode()

来源:互联网 发布:仿凡科 源码 编辑:程序博客网 时间:2024/06/04 00:53

前言

记得刚接触java那会,对于hashCode()方法也是出于朦胧状态,当时为了应付面试,在各种面试宝典背诵和hashCode()相关的面试题,其中在面试宝典中经常出现的就是hashCode和equals的区别。现在依稀记得,如果两个对象的hashCode返回值相同,不能确定这两个对象一定相同,如果两个对象equals()返回true,则对象一定是相同的。还有如果equals()返回false…..。就像绕口令一样,背完之后,过段时间就忘记了,,,然后再继续背。每天背的不亦乐乎。。。。但是对于这里面的原理却依旧很模糊,在日常开发中这个hashCode()到底有什么作用?唉,惭愧!下面来和大家一起总结一下,为了后面不需要再翻无聊的面试宝典!

hashCode()大致介绍

对于hashCode(),我们应该先了解一下哈希表数据结构。根据某一特定值,按照规则映射一个对应的整数值,这些值组合而成的数组称为哈希表。至于哈希表的具体细节这里不深入研究,需要了解的一点就是哈希表是能够快速插入和快速查询的一种数据结构。这也是后面讲述hashCode的关键点。在·java的Object类中有这样一个hashCode()方法

public native int hashCode();

由于是是native方法,所以在Object类中没有具体实现这个hashCode()方法。我们都知道Object类是所有的类的基类。也就是说java中所有类有这个方法,那么这个方法有什么用呢?在java中常用的散列集合都会用到这个hashCode()方法。比如说,我们都知道hashSet中是不能存储重复的对象元素的,那么我们在向集合容器中添加元素的之后,怎么知道这个元素在集合中是否已经存在了呢,有的小伙伴可能会说这不简单嘛,直接调用equals()方法比较一下不就行了嘛。那么如果一个集合容器中已经存储了一万个甚至更多的元素,你现在要将这个新增的元素和一万个容器中已有的元素一个个比较嘛,这种方式确实可以实现,但是效率就比较差了。那么java是怎么实现检查容器的元素和这个新增元素相同呢?于是就有人打算用上面提到的哈希表来实现快速查询了,我们可以再向容器中插入新的元素之前,先调用这个对象的hashCode(),然后将返后的哈希值和哈希表中的数据进行比较(哈希表中的数据是之前新增元素的哈希值),如果不相同就直接插入到容器中,然后再将自己的哈希值保存到哈希表中。如果相同,再找到哈希表中那个相同的哈希值对应对象,用equals来比较他们是否相同,如果equals()返回true,则代表他们是相同的元素对象,就将新增的元素将以前的元素覆盖掉,如果返回false则代表不是同一个对象,那么就需要重新哈希一个新的地址来存储新增的对象元素。
上面的过程听起来很绕,其实就是先利用hashCode方法的映射关系来快速查询可能相同的对象,然后再调用equals()来进行最后的比较。这样就不需要将新增的对象和容器中已有的所有对象一个一个的比较了,这样的效率是不言而喻的。

重写hashCode和equals的必要性

上面说到hashCode()和equals()方法是Object类中的,那么Object中的这两个方法的实用性又如何呢,在Object中的hashCode()方法默认是根据对象在内存中分配的地址来进行哈希映射。同时Object类中默认也有eqauls方法,而默认的equals是比较对象的引用是否相同,那么这两个默认的方法和我们在日常开发的逻辑是否一致呢?具体看下面的小例子。

/** * @ClassName: Student* @Description: TODO(没有重写hashCode和equals的Student类)* @author 爱琴孩*/public class Student {    private String name;    private int age;    public Student(){}    public Student(String name,int age){        this.name=name;        this.age=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;    }}

下面是测试类

/*** @ClassName: Client* @Description: TODO(测试客户端)* @author 爱琴孩*/public class Client {    public static void main(String[] args) {        Student s1=new Student("爱琴孩",20);        System.out.println("s1的hashcode是"+s1.hashCode());        //在日常开发中,我们是希望s1和s2是相同对象        Student s2=new Student("爱琴孩",20);        System.out.println("s2的hashcode是"+s2.hashCode());        Map<Student,Integer> hashMap=new HashMap<Student, Integer>();        hashMap.put(s1, 1);        hashMap.put(s2, 2);        System.out.println("s1.hashCode()==s2.hashCode()---------"+(s1.hashCode()==s2.hashCode()));        System.out.println("s1==s2---------"+(s1==s2));        System.out.println("s1.equals(s2)------------"+s1.equals(s2));        //日常开发逻辑中,我们应该认为s1,s2是同一个元素,应该s2会覆盖s1,所以hashMap的size应该是1        System.out.println("hashMap的size是"+hashMap.size());    }}

测试结果如下
这里写图片描述

图片上面的结果显然不是我们想要的结果,那应该怎么才能实现只保存一个元素呢,首先我们先来看看HashMap中put方法的源码

public V put(K key, V value) {        if (key == null)            return putForNullKey(value);        //根据key来生成哈希值        int hash = hash(key.hashCode());        //然后在原来的哈希表中检查一个哈希值是否存在,如果存在返回对应位置索引        int i = indexFor(hash, table.length);        for (Entry<K,V> e = table[i]; e != null; e = e.next) {            Object k;            //注意这里在hash值相同的前提下,再用equals方法来对key进行比较            //如果都相同则用新的元素值覆盖以前的元素值            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {                V oldValue = e.value;                e.value = value;                e.recordAccess(this);                return oldValue;            }        }     //否则不相同则直接新增新的元素        modCount++;        addEntry(hash, key, value, i);        return null;}

上面的put方法里面可以看出,Object默认的hashCode()和equasl()显然不能满足put()方法中的功能实现,所以我们需要自己重写Student类中的hashCode()和equals()。具体方法重写,现在的IDE可以帮我们自动生成。下面就是eclipse自动重写的hashCode()和equals()。

@Overridepublic int hashCode() {    final int prime = 31;    int result = 1;    result = prime * result + age;    result = prime * result + ((name == null) ? 0 : name.hashCode());    return result;}@Overridepublic boolean equals(Object obj) {    if (this == obj)        return true;    if (obj == null)        return false;    if (getClass() != obj.getClass())        return false;    Student other = (Student) obj;    if (age != other.age)        return false;    if (name == null) {        if (other.name != null)            return false;    } else if (!name.equals(other.name))        return false;    return true;}

下面再来看看测试结果
这里写图片描述
上图可以看出,满足了自己的业务需求,需要注意的一点是,在日常开发中,我们最好同时重写hashCode()和equals(),这样才能实现自己的效果,如果只是重写了hashCode()或者equals()。是达不到效果的,结合上面的put()方法,想必小伙伴们已经知道原因了。这里不再赘述(如果实在不清楚,可以加入我们的大牛长成群,一起探讨)。

联想记忆法来背诵equals()和hashCode()的关系

开始的时候,我也说过,我们很多小伙伴对于hashCode()和equals()的关系不太清楚,到底谁是最终判断两个对象是不是相同的呢,是hashCode()返回值相同,就代表两个对象是同一个对象呢。还是equals()返回true呢?其实要是了解清楚上面的代码,对于这两者之间的关系就会很清楚了。这里再和大家总结一下。
1.如果hashCode()返回值一样,equals不一定是返回true,所以不能确保这两个对象一定是同一个,因为有hash冲突的存在。
2.如果hashCode()返回值不一样,可以肯定是这两个对象肯定不是同一个对象
3.如果是equals()返回true,那么可以确定,hashCode()返回值肯定是相等的,也就是两个对象也是同一个
4.如果equals()返回false,不能确定两个对象的hashCode()一定不相同
其实对于上面的hashCode,我们联想成一个人的姓名,equals()可以联想成一个人的身份证号码,
1.名字相同的人,身份证号可能不相同,他们也可能不是同一个人(因为人名相同的人多了去了。。。)
2.名字不相同的人,那么他的身份证肯定不相同,他们也肯定不是同一个人
3.身份证号相同的人,名字肯定也相同,也就是同一个人
4.身份证不相同的人,他的名字也有可能是相同的
这样联想着记忆是不是会好记一点??

总结

上面只是自己对于hashCode()方法的一点小总结。那些年追过的东西还有很多。。。追逐的路还很长,且行且珍惜!