Equals And HashCode 梳理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Equals And HashCode 梳理相关的知识,希望对你有一定的参考价值。

参考技术A

常言道:"基础不牢,地动山摇"。万丈高楼平地起,不管学习什么语言,在高谈阔论框架和语言应用的同时,也不能忘了语言基础。
本文是关于Equals和HashCode关系梳理的一篇文章,会主要从定义、联系、使用这三个方面围绕展开。

equals()是object里的方法,话不多说,先直接上源码:

正如注释第一句所谈到的, equals就是一个辨别两个对象之间的"相等"关系的函数
当然这种相等关系应由子类自行定义,但注释中指出需遵守以下性质:
性质1: reflexive,自反性 。对于任何非空引用x,x.equals(x)==true应恒成立。
性质2: symmetric,对称性 。对于两个非空引用x和y,x.euqals(y)==true当且仅当y.euqals(x)==true。
性质3: transitive,传递性。 简单地讲,x equals y , y equals z, 则x equals z成立。
性质4: consistent,一致性。 如果两个非空对象x,y没有发生变化,则反复调用x.euqals(y)所返回的结果应一致。
性质5:对于任何非空引用x,x.equals(null)==false成立。

了解完性质,关键的地方来了:

注释中指出,重写equals()则应重写hashcode(),使得equals相等的对象具有相等的hashCode。

hashcode是native方法,具体生成的细节和jdk版本有关,如何生成hashcode并不是今天的主题。接下来,我们看下hashcode的部分注释:

总的来讲:
hashcode的产生是为了hashmap等使用到hashcode的散列存储结构服务的。
hashcode有如下三个性质:
性质1 :一致性:对于同一对象,多次调用hashcode()应返回相同的Integer。
性质2 :equals相等,则hashCode必须相等。
性质3 :equals不等,hashCode不是必须不等。

了解了hashcode()以及equals()之后,肯定会发生这样一个疑问,为什么equals相等,hashcode必须相等?
实际上,这个问题只要搞懂hashcode()的用途,就可以很清楚的明白了。
hashcode()产生的目的就是为了hashMap等散列存储结构提供支持。
提供什么样的支持?
首先要明白,散列结构存储元素的重要过程之一是判别元素的“相同”/“不同”,这个相同与否是根据元素的hash值(为了降低hash冲突的概率,不同结构计算hash值的方式不同,但都是基于hashCode)决定的。每次put元素,则计算对应元素的hash值以及对应的坐标 ,有hash冲突则解决hash冲突,没有就直接存放。
这样做的好处是 ,当我要向一个散列结构存储一个元素时,我判别是否有重复元素,只需要计算hash值一次并比较,而不是对已经存储的每个元素依次equals(想象一个巨大的hashset,每次存放一个元素要和所有元素依次equals,这效率该多么低下)。
所以问题来了 ,如果散列结构存储的元素重写了equals方法却没有重写hashcode()方法,就会导致一个问题:逻辑上我们认为相同的对象(equals==true),在散列存储结构的存储中因为hashcode的不同,最终被判定为两个不同的对象,从而存放了两份。
显然这并不是我们想要的。
下面附上一个例子,来展示下

这里创建了一个Person对象,重写了equals方法,equals方法的逻辑是如果是同一个对象或者两个Person对象的名字和年龄相等,则认为是“相等”的。

这里我们创建了两个我们认为逻辑上相等的对象,使用hashset测试结果为:

显然hashset将其认为是两个对象,分别存储了。
所以我们现在要做的就是重写hashCode()方法,确保equals相等的实例hashcode()也相等。

不要小瞧equals()和hashcode()的重写,如果不恰当的重写,会导致一系列难以预料的结果产生。

在上面的person例子中,对equals的重写,我参照了String的equals重写。
大致逻辑是 判断是否为同一个对象,如果否,判断是否相同类型且逻辑相等。
实际上《Effective Java》这本书中提出了一个关于重写equals的规范,可以进行参考。

对于equals的重写, 最应该记住的是传入的应该是object! (最好使用@Override来帮助检查),否则就不是重写而是重载了!

当然,在重写完equals后最好对其进行单元测试,看其是否符合之前所讲的equals方法所强调的性质 。如果你是android开发人员,简单的junit单元测试可以参照我之前的一篇博客。 https://www.jianshu.com/p/5fea0dcc53b6

同样的《Effective Java》这本书中提出了一个关于重写HashCode方法的建议,可以进行参考。

我们参照上面的建议,重写一下Person类的hashcode():

在未测试的情况下这个hashcode()的冲突处理是否合适还需考量,但逻辑上它确实保证了与equals方法的同步。
现在再进行测试,发现已经搞定。

看到《Effective Java》建议的你,一定会有些疑问,why must 31?
事实上,如果你看过String(Android jdk)的hashCode源码,你会发现它是这样的

它竟然也使用了31这个数字!!
所以这背后的原理是什么呢?31又有怎样的好处呢?
《Effective Java》里也进行了解释:

这段话里已经讲的比较明确了:
1.选择奇数,防止溢出信息丢失。(实际这一点我还不太明白,有懂的老哥可以讲讲)
2.选择素数,because its traditional。(总的来讲就是和是否是素数关系不大。可以看下面的StackOverFlow链接,许多人认为和是否为素数没关系,毕竟所有的素数都是奇数(除了2))
3.乘以31,可以被优化为位运算,就会比传统的乘法快很多。

所以为什么必须是31呢?41,51不行吗?
https://stackoverflow.com/questions/299304/why-does-javas-hashcode-in-string-use-31-as-a-multiplier
stackOverFlow的这篇问答里有一个老哥做了测试:

也就是说,如果对超过50000个英语单词做hash运算,并采用31,33,37,39,41等做为常数乘,结果冲突次数都在7次以内。
那么31是最接近2幂次的数字,优化为位运算更简洁,自然采用31。

这篇博客在草稿箱里也待了好几天,一直耽搁着没有发出来。今天完成了发出来真是畅快啊。

Relationship between hashCode and equals method in Java

转自stackoverflow:

Q:

I read in many places saying while override equals method in Java, should override hashCodemethod too, otherwise it is "violating the contract".

But so far I haven‘t faced any problem if I override only equals method, but not hashCode method.

What is the contract? And why am I not facing any problem when I am violating the contract? In which case will I face a problem if I haven‘t overridden the hashCode method?

 

A:

The problem you will have is with collections where unicity of elements is calculated according to both .equals() and .hashCode(), for instance keys in a HashMap.

As its name implies, it relies on hash tables, and hash buckets are a function of the object‘s .hashCode().

If you have two objects which are .equals(), but have different hash codes, you lose!

The part of the contract here which is important is: objects which are .equals() MUST have the same .hashCode().

 

According to the doc, the default implementation of hashCode will return some integer that differ for every object

As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation
technique is not required by the JavaTM programming language.)

However some time you want the hash code to be the same for different object that have the same meaning. For example

This kind of problem will be occur if you use a hash data structure in the collection framework such as HashTable, HashSet. Especially with collection such as HashSet you will end up having duplicate element and violate the Set contract.

以上是关于Equals And HashCode 梳理的主要内容,如果未能解决你的问题,请参考以下文章

Java hashCode() and equals() – Contract, rules and best practices

Java hashCode() and equals() – Contract, rules and best practices

Relationship between hashCode and equals method in Java

Java中如何判断两个对象是否相等(Java equals and ==)

集合框架比较两个对象是否相同(equals和hashCode方法)

Java中如何判断两个对象是否相等(Java equals and ==)