在我们平时编写Java代码时,重写equals方法时一定要重写hashCode方法,这是为什么呢?
在讨论这个问题前,我们先看下Object类中hashCode方法和equals方法。
hashCode方法:
翻译如下:
equals方法:
翻译如下:
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。
hash方法会调用对象的hashCode()方法:
addEntry方法添加新节点:
new一个Entry实例,next指向原有的Entry实例。也就是新new的Entry实例是该链表的头。
Entry是一个静态内部类,有一个属性next,指向下一个Entry,形成一个链表结构。
2、equals方法和hashCode方法
看下面代码:
输出结果:
我们Student类重写了equals方法,hashCode方法没有重写,s1和s2的姓名和年龄相同,equals方法为true,认为是同一个人。但是s1和s2的hashCode返回不同。
我们看下hashMap的get方法,先获取key的hashCode,由于s1和s2的hashCode不同,所以hashMap.get(s2)得到的是null。
接下来我们重写下Student类的hashCode方法,让equals方法和hashCode方法始终在逻辑上保持一致性。
重新运行,输出结果如下,s1和s2的hashCode相同了,hashMap.get(s2)得到了1。
① 在 Java 应用程序执行期间,在对同一对象多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是将对象进行 equals 比较时所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
② 如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
③ 如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不 要求一定生成不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同整数结果可以提高哈希表的性能。
如果hashCode方法依赖于对象中易变的数据,用户就要当心了,因为此数据发生变化时,hashCode()方法就会生成一个不同的hash值。看下面例子:
修改了age属性的值,导致hashCode变化,所以输出为“null”。
因此,在设计hashCode方法和equals方法的时候,如果对象中的数据易变,则最好在equals方法和hashCode方法中不要依赖于该字段。