Java中的equals()和hashCode()

概述

在我们使用类集框架(比如使用hashMap、hashSet)的时候,经常会涉及到重写equals()和hashCode()这两个方法。

这两个方法的联系是: 1. 如果两个对象不同,那么他们的hashCode肯定不相等; 2. 如果两个对象的hashCode相同,那么他们也未必相等。

所以说,如果想在hashMap里面让两个不相等的对象对应同一个值,首先需要让他们的hashCode相同,其次还要让他们的equals()方法返回true,因此为了达到这个目的,我们就只能重写hashCode()和equals()这两个方法了。

引用一篇文章的解说:The idea behind a Map is to be able to find an object faster than a linear search. Using hashed keys to locate objects is a two-step process. Internally the Map stores objects as an array of arrays. The index for the first array is the hashcode() value of the key. This locates the second array which is searched linearly by using equals() to determine if the object is found.

大致意思就是:使用Map比线性搜索要快。Map存储对象是使用数组的数组(可以理解为二维数组,这并不准确,不过可以按照这个理解。之前hashMap用的是数组,每个数组节点对应一个链表,现在Java 8已经把链表改成了treeMap,相当于一个二叉树,这样检索的时候比链表更快,尤其是在最坏情况下,由原来链表的O(n)变成了二叉树的O(logn),详见https://dzone.com/articles/hashmap-performance),所以搜索大致是分为两步的,第一步是根据hashCode寻找第一维数组的下标,然后根据equals的返回值判断对象是第二维数组中的哪一个。

例证

举个栗子——

import java.util.HashMap;{private String color;public Apple(String color) {this.color = color;}(String[] args) {Apple a1 = new Apple(“green”);Apple a2 = new Apple(“red”);//hashMap stores apple type and its quantityHashMap<Apple, Integer> m = new HashMap<Apple, Integer>();m.put(a1, 10);m.put(a2, 20);System.out.println(m.get(new Apple(“green”)));}}

程序的运行结果为:null 此时,我们已经向hashMap里面存储两个对象了,且a1就是green Apple,那么为什么我们通过”green”去查找却返回null呢? 显然,后来我们新new出来一个对象,这和之前加入的a1绿苹果那个对象绝对不是同一个对象,根据终极父类Object中的hashCode()的计算结果,其返回值绝对是不一样的。

所以——

第一步:重写hashCode()

我们需要先让“凡是color属性相同的对象,其hashCode都一样”,所以我们可以这样重写hashCode():

(){return this.color.length();}

这里我们使用color属性的内容的长度作为hashCode的大小,那么凡是green的苹果,hashCode肯定都是5(green字符串长度为5),这样一来,属性相同的对象的hashCode肯定都相同了。这只是保证了一维数组的下标找到了(姑且这样理解),还需要找第二维的下标呢,这个需要在第二步中解决。在解决第二步之前,你可能会有问题——这样一来的话,如果有black苹果(假设有black),那么它的hashCode也变成了5了啊,和green一样了。这个同样靠第二步解决。

第二步:重写equals()

我们已经知道,如果想让两个对象一样,除了让他们的hashCode值一样外,还要让他们的equals()函数返回true,两个都符合才算一样。所以第二步我们要重写equals()函数,使“只要color一样,两个苹果就是相同的”:

(Object obj) {;;return this.color.equals(((Apple) obj).color); //如果颜色相同,也返回true}

这样一来,根据最后一句话,凡是颜色相同的苹果,第二维也映射到同一个位置了(姑且这么理解)。这样一来,就可以根据颜色在hashMap里寻找苹果了。 把我们重写过的hashCode()和equals()加入到之前的代码中,便会输出结果:10,即键a1所对应的值。

结语

感觉这篇文章涉及的内容还是相当基础和重要的。文章到此也差不多可以结束了,另附上以前学习时记的笔记,感觉还是挺有用的,我自己的笔记自己看起来自然是毫无障碍的,不过实在不想整理了,就直接贴上来吧,大家将就将就看看吧,可以作为上面内容的唠叨和补充。

附录1

HashSet在存储的时候(比如存的是字符串),则存进去之后按照哈希值排序(也就意味着遍历的时候得到的顺序不是我们添加的顺序,即乱序),如果第二个对象和第一个Hash值一样但是对象不一样,则第二个会链在第一个后面。在添加对象的时候,add()返回boolean型,如果添加的对象相同(比如两个相同的字符串),则返回false,添加失败。

HashSet如何保证元素的唯一性? 通过元素的方法——hashCode()和equals()来实现。 如果两个元素的hashCode不同,直接就存了; 如果两个元素的hashCode相同,则调用equals判断是否为true,true则证明是同一个对象,就不存了,false的话证明是不同的对象,存之。

一旦自定义了对象,想要存进HashSet,则一定要覆写hashCode()和equals()方法—— 比如我们定义Person类,仅含有name,age两个参数,规定:只要姓名和年龄相同,就断定为“同一个人”,则不能存入HashSet,否则的话可以。 对于这种情况,如果我们new出来几个人,其中存在名字和年龄相同的,则均会存入HashSet,原因就是这些对象是不同的对象,所占内存不一样,则通过hashCode()返回的哈希值也都不一样,所以理所当然的存入了HashSet。为了避免把“同一个人”存进HashSet,我们首先需要让hashCode()针对“同一个人”返回相同的哈希值,即覆写hashCode()方法!

(){return 110;}

这样自然也可以,不过没有必要让所有的对象返回的哈希值都一样,只要“同一个人”的哈希值一样就行了,所以写成这样更好:

(){return name.hashCode() + age;}

这样的话“同一个人”返回的哈希值就是相同的。不过这样还是不够完美,因为覆写的这个hashCode()虽然会让“相同的人”返回相同的哈希值,但也可能会让“不同的人”返回相同的哈希值,比如两个人name不同,age不同,但name的哈希值加上age恰恰相同,这样的话就坑爹了。为了避免这种现象,让这种哈希值恰巧撞上的概率进一步减小,我们写成这样会更好:

(){return name.hashCode() + age * 19;}爱上一个人的时候,总会有点害怕,怕得到他;怕失掉他。

Java中的equals()和hashCode()

相关文章:

你感兴趣的文章:

标签云: