资讯专栏INFORMATION COLUMN

Java 用自定义类型作为HashMap的键

张率功 / 2855人阅读

摘要:检查对应位置中的对象和当前对象是否相等。重载是为了向表明当前对象和上所保存的对象是相等的,这样我们才真正地获得了这个所对应的这个键值对。以下例子可以作为上述说明的佐证以上即是全部。

这是Java中很经典的问题,在面试中也经常被问起。其实很多书或者文章都提到过要重载hashCode()equals()两个方法才能实现自定义键在HashMap中的查找,但是为什么要这样以及如果不这样做会产生什么后果,好像很少有文章讲到,所以写这么一篇来说明下。

首先,如果我们直接用以下的Person类作为键,存入HashMap中,会发生发生什么情况呢?

javapublic class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }
}
javaimport java.util.HashMap;

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

        HashMap map = new HashMap();

        map.put(new Person("001"), "findingsea");
        map.put(new Person("002"), "linyin");
        map.put(new Person("003"), "henrylin");
        map.put(new Person("003"), "findingsealy");

        System.out.println(map.toString());

        System.out.println(map.get(new Person("001")));
        System.out.println(map.get(new Person("002")));
        System.out.println(map.get(new Person("003")));
    }
}

那么输出结果是什么呢?

{Person@6e4d4d5e=henrylin, Person@275cea3=findingsea, Person@15128ee5=findingsealy, Person@4513098=linyin}
null
null
null

我们可以看到,这里出现了两个问题:

在添加的过程中,我们将key=new Person("003")的键值对添加了两次,那么在期望中,HashMap中应该只存在一对这样的键值对,因为key(期望中)是相同的,所以不应该重复添加,第二次添加的value="findingsealy"应该替换掉原先的value="henrylin"。但是在输入中,我们发现期望中的情况并没有出现,而是在HashMap同时存在了value="findingsealy"value="henrylin"的两个键值对,并且它们的key值还是不相同的,这显然是错误的。

在获取value值时,我们分别用三个Person对象去查找,这三个对象和我们刚刚存入的三个key值(在期望中)是相同的,但是查找出的却是三个null值,这显然也是错误的。

那么,正确的方法其实在很多地方都是被描述过了,直接对Person类进行修改,重载equalshashCode方法,修改过后的Person类如下:

javapublic class Person {

    private String id;

    public Person(String id) {
        this.id = id;
    }

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

        Person person = (Person) o;

        if (id != null ? !id.equals(person.id) : person.id != null) return false;

        return true;
    }

    @Override
    public int hashCode() {
        return id != null ? id.hashCode() : 0;
    }
}

那么,当我们重新执行上述的检验程序时,得到的结果如下:

{Person@ba31=findingsea, Person@ba32=linyin, Person@ba33=findingsealy}
findingsea
linyin
findingsealy

可以看到,之前指出的亮点错误都得到了改正。那么,为什么会这样呢?

HashMap中,查找key的比较顺序为:

计算对象的Hash Code,看在表中是否存在。

检查对应Hash Code位置中的对象和当前对象是否相等。

显然,第一步就是要用到hashCode()方法,而第二步就是要用到equals()方法。在没有进行重载时,在这两步会默认调用Object类的这两个方法,而在Object中,Hash Code的计算方法是根据对象的地址进行计算的,那两个Person("003")的对象地址是不同的,所以它们的Hash Code也不同,自然HashMap也不会把它们当成是同一个key了。同时,在Object默认的equals()中,也是根据对象的地址进行比较,自然一个Person("003")和另一个Person("003")是不相等的。

理解了这一点,就很容易搞清楚为什么需要同时重载hashCode()equals两个方法了。

重载hashCode()是为了对同一个key,能得到相同的Hash Code,这样HashMap就可以定位到我们指定的key上。

重载equals()是为了向HashMap表明当前对象和key上所保存的对象是相等的,这样我们才真正地获得了这个key所对应的这个键值对。

还有一个细节,在Person类中对于hashCode()的重在方法为:

java@Override
public int hashCode() {
    return id != null ? id.hashCode() : 0;
}

这里可能有疑惑的点在于:为什么可以用String类型的变量的Hash Code作为Person类的Hash Code值呢?这样new Person(new String("003"))new Person(new String("003"))Hash Code是相等的吗?

来看看以下代码的输出:

javaSystem.out.println("findingsea".hashCode());
System.out.println("findingsea".hashCode());
System.out.println(new String("findingsea").hashCode());
System.out.println(new String("findingsea").hashCode());
728795174
728795174
728795174
728795174

可以看到四条语句的输出都是相等的,很直观的合理的猜测就是String类型也重载了hashCode()以根据字符串的内容来返回Hash Code值,所以相同内容的字符串具有相同的Hash Code

同时,这也说明了一个问题:为什么在已知hashCode()相等的情况下,还需要用equals()进行比较呢?就是因为避免出现上述例子中的出现的情况,因为根据对Person类的hashCode()方法的重载实现,Person类会直接用id这个String类型成员的Hash Code值作为自己的Hash Code值,但是很显然的,一个Person("003")和一个String("003")是不相等的,所以在hashCode()相等的情况下,还需要用equals()进行比较。

以下例子可以作为上述说明的佐证:

javaSystem.out.println(new Person("003").hashCode()); // 47667
System.out.println(new String("003").hashCode()); // 47667

System.out.println(new Person("003").equals(new String("003"))); // false

以上即是全部。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/64320.html

相关文章

  • 1、Map接口 2、模拟斗地主洗牌发牌

    摘要:中的集合称为单列集合,中的集合称为双列集合。洗牌通过数字完成洗牌发牌发牌将每个人以及底牌设计为将最后张牌直接存放于底牌,剩余牌通过对取模依次发牌。存放的过程中要求数字大小与斗地主规则的大小对应。 01Map集合概述 A:Map集合概述: 我们通过查看Map接口描述,发现Map接口下的集合与Collection接口下的集合,它们存储数据的形式不同  a:Collection中的集...

    付伦 评论0 收藏0
  • map集合的学习

    摘要:提供了专门的集合类用来存放这种对象关系的对象,即接口。中的集合,元素是成对存在的理解为夫妻。中的集合称为单列集合,中的集合称为双列集合。根据指定的键,在集合中获取对应的值。 day04 【Map】 主要内容 Map集合 教学目标 [ ] 能够说出Map集合特点 [ ] 使用Map集合添加方法保存数据 [ ] 使用键找值的方式遍历Map集合 [ ] 使用键值对的方式遍历Map集合 [ ...

    peixn 评论0 收藏0
  • Java集合框架——Map接口

    摘要:第三阶段常见对象的学习集合框架集合在实际需求中,我们常常会遇到这样的问题,在诸多的数据中,通过其编号来寻找某一些信息,从而进行查看或者修改,例如通过学号查询学生信息。面试题和的区别是单列集合的顶层接口,有子接口和。 第三阶段 JAVA常见对象的学习 集合框架——Map集合 showImg(https://segmentfault.com/img/remote/1460000019683...

    princekin 评论0 收藏0
  • 求数组差/交集函数-php数组函数(二)

    摘要:求数组差集函数函数只检查了多维数组中的一维。自定义函数必须返回一个小于零,等于零,或大于零的整数。用自定义函数比较的值,函数参数为数组的值。 求数组差集函数 函数只检查了多维数组中的一维。可以用 array_diff($array1[0], $array2[0]) 检查更深的维度。 u:自定义函数比较,a(association):同时比较键和值。 自定义函数callable $v...

    ChristmasBoy 评论0 收藏0
  • Java 集合Hashtable源码深入解析

    摘要:分别获取正序反序的键集。是用来实现机制的第部分源码解析基于为了更了解的原理,下面对源码代码作出分析。实现了迭代器和枚举两个接口获取的迭代器若的实际大小为则返回空迭代器对象否则,返回正常的的对象。 概要 前面,我们已经系统的对List进行了学习。接下来,我们先学习Map,然后再学习Set;因为Set的实现类都是基于Map来实现的(如,HashSet是通过HashMap实现的,TreeSe...

    Turbo 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<