前言:
大家都知道,在面试时有两个关于方法equals和hashCode的高频面试题:
- a.equals(b)为true,那a.hashCode一定等于b.hashcode吗?
- 为什么重写equals的同时要重写hashCode方法?
就此,我想浅谈一下自己对于关于equals方法和hashCode两个方法及这两道面试题的理解;
一.a.equals(b)为true,那a.hashCode()一定等于b.hashcode()吗?
首先我们给出第一个问题的答案:在两个方法都没有被重写的情况下,一定,在说原因前我们先看看这两个方法在Object类中的源代码;
我们都知道,Object是所有类的超类,可以看到,equals在Obejct类中的定义为使用==简单的比较两个对象的值;
//equals方法
public boolean equals(Object obj) {
return (this == obj);
}
而hashCode方法则是一个本地实现的方法;
//hashCode方法
public native int hashCode();
这里就不得不提到关于修饰词native方法的概念:
想必读者已经了解过native关键字了。这里笔者就大致囊括一下,被native关键字修饰的方法叫做本地方法,本地方法和其它方法不一样,本地方法意味着和平台有关,因此使用了native的程序可移植性都不太高。另外native方法在JVM中运行时数据区也和其它方法不一样,它有专门的本地方法栈。native方法主要用于加载文件和动态链接库,由于Java语言无法访问操作系统底层信息(比如:底层硬件设备等),这时候就需要借助C语言来完成了。被native修饰的方法可以被C语言重写。
这是关于native的描述,简单来说,就是这个方法并不由Java代码来实现,而是具有平台特性,以下是jdk8API文档中对于hashCode方法的具体描述:
public int hashCode()返回对象的哈希码值。 支持这种方法是为了散列表,如HashMap提供的那样 。
hashCode
的总合同是:
- 只要在执行Java应用程序时多次在同一个对象上调用该方法,
hashCode
方法必须始终返回相同的整数,前提是修改了对象中equals
比较中的信息。 该整数不需要从一个应用程序的执行到相同应用程序的另一个执行保持一致。- 如果根据
equals(Object)
方法两个对象相等,则在两个对象中的每个对象上调用hashCode
方法必须产生相同的整数结果。- 不要求如果两个对象根据equals(java.lang.Object)方法不相等,那么在两个对象中的每个对象上调用
hashCode
方法必须产生不同的整数结果。 但是,程序员应该意识到,为不等对象生成不同的整数结果可能会提高哈希表的性能。尽可能多的合理实用,由类别
Object
定义的hashCode方法确实为不同对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但Java的编程语言不需要此实现技术。)--引用至JDK8 API文档
概述下来就是Java规定了,在实现hashCode方法时必须遵循以下原则:
- 如果两个对象调用equals方法返回为true,那两个对象调用hashCode返回值必须相同;
- 如果两个对象调用equals方法返回为false,这两个对象调用hashCode的返回值可以相同也可以不同;
- 多次调用同一个对象的hashCode方法,返回值一定是相同的;
- hashCode方法是为了对散列表提供支持;
从以上Java的规定中,我们就可以得到第一个问题的答案:即调用equals方法比较两个对象返回值为true,则两个对象调用hashCode方法的返回值一定相同;
二.为什么重写equals方法的同时要重写hashCode方法?
其原因大致可以从以下几点来说明:
- 为了遵循Java的原则
- 为了满足业务的需求
- 为了提升散列表的效率
1.为了遵循Java的原则
Java在api说明文档中明确的说明了关于Object类中hashCode方法的实现规则,即hashCode返回值必须要服从于equals的返回结果;而Object类又是所有类的超类,Object其中存在的特性应该是所有类都必须共有的,所以所有类理论上所有的类都应该满足在Object中hashCode和equals两个方法之间的从属关联性,同时我们都知道,IDEA在使用快捷键重写方法时,hashCode和equals是建议同时重写的
这也从侧面印证了这两个方法之间的关系就该是正常情况下所有类共有的特性 ,重写equals方法同时重写hashCode是对这种特性的最佳实践;
2.为了满足业务的需求
我们需要重写equals是因为我们对两个对象有着比较是否相等的需求,而这两个对象的类往往是一些具体事物在Java中的映射;
拿Student类来举例,当我们比较两个student是否相等时,我们的业务需求上的判断条件应该是:两个学生的身份证号,长相,年龄,爱好等等所有属性都相等,我们就认为这两个对象是相等的;
但大家都知道,在Java中两个对象的属性相等,并不一定代表着两个对象的引用相等,这时就需要我们手动的重写equals方法来满足我们对于业务的需求(两个对象属性相同,则代表两个对象相同)
在满足业务需求的同时,我们也需要遵循Java的原则,所以在重写equals时,应该重写hashCode方法;
3.为了提升散列表的效率
我们前面提到过,在Java文档中说明了支持hashCode方法是为了散列表;
散列表的一个特性是;其中不能存在相同的key值的元素;
所以我们每次向散列表中存入元素时,都会比较存入元素的key值是否与散列表中现有元素的key值相等,那么这种比较又该如何来实现呢?难道要每次存入的时候都要使用equals方法对原有的元素进行遍历比较吗,这种方案时间复杂度会达到O(n),效率无疑是非常低下的;
而散列表的另一个特性是:元素hashCode方法的值,决定了元素插入的散列桶下标;
在这种条件下,如果一个元素试图存首入散列表,先会判断其hashCode值对应的散列桶中是否存在元素,如果不存在,则直接存入,如果存在,则只需要对这个散列桶中的所有元素进行key值的equals比较,这样的解决方法无疑大大的提高了散列表的效率,当然不止是插入,查询时的快速定位也是相同的道理,在理想的情况下,时间复杂度可以降低到O(1)(每个散列桶中都只有一个元素时);
以上就是我关于关于equals方法和hashCode方法的一些简单理解,不足之处还请指正;