热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《EffectiveJava》阅读笔记9覆盖equals时总要覆盖hashCode

1.什么是hashcode方法?hashcode方法返回对象的哈希码值在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有改变&
1. 什么是hashcode方法?

hashcode方法返回对象的哈希码值

  • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有改变,那么对于这同一个对象调用多次,hashcode方法都必须返回同一个整数。
  • hashcode的存在主要用于查找的快捷性,如Hashtable,HashMap等,hashcode是用来在散列存储结构中确定对象的存储地址的。

2. hashcode相等与对象相等之间的关系:(保证设计是规范的前提下)
  • 如果两个对象相同,那么两个对象的hashcode也必须相同。

  • 如果两个对象的hashcode相同,并不一定表示两个对象就相同,也就是不一定适合equals方法,只能够说明两个对象在散列表存储结构中,“存放在同一个篮子里”。


3. 为什么要覆盖hashcode

每个覆盖 equals 方法的类中,也必须覆盖 hashCode 方法。如果不这样做的话,就会违反 Object.hashCode 的通用约定,这个约定的内容如下:
摘自 Object 规范[JavaSE6]:

  • 在应用程序的执行期间,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法都必须始终如一的返回同一个整数。在同一个应用程序的多次执行过程中,每次执行所返回的整数可以不一致。
  • 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCode方法都必须产生同样的整数结果
  • 如果两个对象根据equals(Object)方法比较是不相等的,那么调用这两个对象中任意一个对象的hashCode方法,则不一定要产生不同的整数结果。但是程序员应该都知道,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的整体性能
    如果不覆盖hashCode方法,我们在需要用到hashCode的地方可能不会如我们所愿,下面看个例子,有这么一个类,我们只覆盖了equals方法,没有覆盖hashCode方法:

package com.atguigu.nio;import java.util.HashMap;
import java.util.Map;
import java.util.Objects;public class Tesz {private final String field01;public Tesz(String field01) {this.field01 &#61; field01;} //覆盖equals方法&#64;Overridepublic boolean equals(Object o) {if (this &#61;&#61; o){return true;}if (o &#61;&#61; null || getClass() !&#61; o.getClass()) {return false;}Tesz myObject &#61; (Tesz) o;return (Objects.equals(field01, myObject.field01));}public static void main(String[] args) {Map map &#61; new HashMap<>();map.put(new Tesz("123"), "123");System.out.println(map.get(new Tesz("123")));}
}

通过运行的结果我们可以看到key是new MyObject(“123”)时&#xff0c;value是null&#xff0c;从而我们知道即使覆盖了equals方法后还是不能保证相等&#xff0c;原因在于该类违反了hashCode的约定&#xff0c;由于MyObject没有覆盖hashCode方法&#xff0c;导致两个相等的实例拥有不相等的散列码&#xff0c;put方法把此对象放在一个散列桶中&#xff0c;get方法从另外一个散列桶中查找这个对象&#xff0c;这显然是无法找到的
在这里插入图片描述
当我们加入hashCode方法后就正确显示结果了。

//至于hashCode方法怎么写&#xff0c;返回的哈希值参考是什么&#xff0c;可以参考&#xff1a;http://blog.csdn.net/zuiwuyuan/article/details/40340355
&#64;Override
public int hashCode() {int result &#61; field01.hashCode() * 17;return result;
}

在这里插入图片描述
源码分析
为什么会出现这种情况呢&#xff0c;下面我们看一看Map的源码就清楚了&#xff1a;

final Node getNode(int hash, Object key) {Node[] tab; Node first, e; int n; K k;/*** 检查table是否为空&#xff0c;table为HashMap实例中的一个存放数据的数组* 检查第一个元素的hash码是否为null*/if ((tab &#61; table) !&#61; null && (n &#61; tab.length) > 0 &&(first &#61; tab[(n - 1) & hash]) !&#61; null) {/** 效率方面的考虑* 总是将第一个对象拿出来比较&#xff0c;如果符合要求直接返回* 避免执行循环准备工作的一系列代码*/if (first.hash &#61;&#61; hash && // always check first node((k &#61; first.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))return first;//进入循环之前的一系列准备以及检查工作if ((e &#61; first.next) !&#61; null) {if (first instanceof TreeNode)return ((TreeNode)first).getTreeNode(hash, key);do {//循环内部利用&&运算符的惰性&#xff0c;如果hash码不相同就直接跳过了&#61; &#61;if (e.hash &#61;&#61; hash &&((k &#61; e.key) &#61;&#61; key || (key !&#61; null && key.equals(k))))return e;} while ((e &#61; e.next) !&#61; null);}}return null;}

put方法把电话号码对象存放在一个散列桶(hash bucket)中&#xff0c;get方法却在另外一个散列桶里面查找。即使这两个实例正好被放到了同一个散列桶里面&#xff0c;get方法也一定会返回null&#xff0c;因为HashMap做了一项优化&#xff0c;将每个项相关联的散列码存放起来&#xff0c;如果hash码不匹配&#xff0c;则不会去检验对象的等同性&#xff0c;充分利用了逻辑与运算的惰性(&&)。

4. 如何在覆盖equals方法时覆盖hashcode方法&#xff1f;

实际上&#xff0c;问题很简单&#xff0c;只要我们重写hashcode方法&#xff0c;返回一个适当的hash code即可。

&#64;Override
public int hashCode() {
return 42;
}

这样的确能解决上面的问题&#xff0c;但实际上&#xff0c;这么做&#xff0c;会导致很差的性能&#xff0c;因为它总是确保每个对象都具有同样的散列码。因此&#xff0c;每个对象都被映射到同一个散列桶中&#xff0c;使得散列表退化成链表。

一个好的散列函数通常倾向于“为不相等的对象产生不同的散列码”。
理想情况下&#xff0c;散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上。但实际上&#xff0c;要达到这种理想的情形是非常困难的。

如何设置一个好的散列函数&#xff1f;
a. 为对象计算int类型的散列码:
· 对于boolean类型&#xff0c;计算(f?1:0)
· 对于byte,char,short,int类型&#xff0c;则计算(int)f
· 对于long类型&#xff0c;计算(int)(f^(f>>>32))
· 对于float类型&#xff0c;计算Float.floatToIntBits(f)
· 对于Double类型&#xff0c;计算Double.doubleToLongBits(f)&#xff0c;然后再按照long类型处理
· 对于对象引用&#xff0c;并且该类的equals方法通过递归地调用equals的方式来比较这个域&#xff0c;则同样为这个域递归地调用hashcode
· 对于数组&#xff0c;则把每一个元素当作单独的域来处理。

b. 将获取到的c合并&#xff1a;result &#61; 31 * reuslt &#43; c;
c. 返回result

比如&#xff0c;我们可以优化上面的hashcode方法:

&#64;Overridepublic int hashCode() {int result &#61; 17;result &#61; 31 * result &#43; areaCode;result &#61; 31 * result &#43; prefix;result &#61; 31 * result &#43; lineNumber;return result;}

如果一个类是不可变类&#xff0c;并且计算散列码的开销也比较大&#xff0c;就应该考虑吧散列码缓存在对象内部&#xff0c;而不是每次请求的时候都重新计算散列码

private volatile static int hashcode;&#64;Overridepublic int hashCode() {int result &#61; hashcode;if (result &#61;&#61; 0){result &#61; 31 * result &#43; areaCode;result &#61; 31 * result &#43; prefix;result &#61; 31 * result &#43; lineNumber;hashcode &#61; result;}return result;}

为什么要选31&#xff1f;

因为它是个奇素数&#xff0c;另外它还有个很好的特性&#xff0c;即用移位和减法来代替乘法&#xff0c;可以得到更好的性能&#xff1a;

31*i &#61;&#61; (i<<5)-i

4. 参考文献

https://www.cnblogs.com/Tony-Anne/p/6727671.html
https://www.jianshu.com/p/40ee40f155aa

在这里插入图片描述

本公众号分享自己从程序员小白到经历春招秋招斩获10几个offer的面试笔试经验&#xff0c;其中包括【Java】、【操作系统】、【计算机网络】、【设计模式】、【数据结构与算法】、【大厂面经】、【数据库】期待你加入&#xff01;&#xff01;&#xff01;

1.计算机网络----三次握手四次挥手
2.梦想成真-----项目自我介绍
3.你们要的设计模式来了
4.一字一句教你面试“个人简介”
5.接近30场面试分享
6.你们要的免费书来了


推荐阅读
  • Android LED 数字字体的应用与实现
    本文介绍了一种适用于 Android 应用的 LED 数字字体(digital font),并详细描述了其在 UI 设计中的应用场景及其实现方法。这种字体常用于视频、广告倒计时等场景,能够增强视觉效果。 ... [详细]
  • 本文介绍了Java并发库中的阻塞队列(BlockingQueue)及其典型应用场景。通过具体实例,展示了如何利用LinkedBlockingQueue实现线程间高效、安全的数据传递,并结合线程池和原子类优化性能。 ... [详细]
  • golang常用库:配置文件解析库/管理工具viper使用
    golang常用库:配置文件解析库管理工具-viper使用-一、viper简介viper配置管理解析库,是由大神SteveFrancia开发,他在google领导着golang的 ... [详细]
  • 本文详细介绍了Java中org.neo4j.helpers.collection.Iterators.single()方法的功能、使用场景及代码示例,帮助开发者更好地理解和应用该方法。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • Java 类成员初始化顺序与数组创建
    本文探讨了Java中类成员的初始化顺序、静态引入、可变参数以及finalize方法的应用。通过具体的代码示例,详细解释了这些概念及其在实际编程中的使用。 ... [详细]
  • 1:有如下一段程序:packagea.b.c;publicclassTest{privatestaticinti0;publicintgetNext(){return ... [详细]
  • 深入理解Cookie与Session会话管理
    本文详细介绍了如何通过HTTP响应和请求处理浏览器的Cookie信息,以及如何创建、设置和管理Cookie。同时探讨了会话跟踪技术中的Session机制,解释其原理及应用场景。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 将Web服务部署到Tomcat
    本文介绍了如何在JDeveloper 12c中创建一个Java项目,并将其打包为Web服务,然后部署到Tomcat服务器。内容涵盖从项目创建、编写Web服务代码、配置相关XML文件到最终的本地部署和验证。 ... [详细]
  • 从 .NET 转 Java 的自学之路:IO 流基础篇
    本文详细介绍了 Java 中的 IO 流,包括字节流和字符流的基本概念及其操作方式。探讨了如何处理不同类型的文件数据,并结合编码机制确保字符数据的正确读写。同时,文中还涵盖了装饰设计模式的应用,以及多种常见的 IO 操作实例。 ... [详细]
  • 本文介绍了如何通过 Maven 依赖引入 SQLiteJDBC 和 HikariCP 包,从而在 Java 应用中高效地连接和操作 SQLite 数据库。文章提供了详细的代码示例,并解释了每个步骤的实现细节。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文详细介绍了Akka中的BackoffSupervisor机制,探讨其在处理持久化失败和Actor重启时的应用。通过具体示例,展示了如何配置和使用BackoffSupervisor以实现更细粒度的异常处理。 ... [详细]
author-avatar
xjoliemonicane_934
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有