热门标签 | 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.你们要的免费书来了


推荐阅读
  • JavaScript 基础语法指南
    本文详细介绍了 JavaScript 的基础语法,包括变量、数据类型、运算符、语句和函数等内容,旨在为初学者提供全面的入门指导。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
  • 本文将详细探讨 Java 中提供的不可变集合(如 `Collections.unmodifiableXXX`)和同步集合(如 `Collections.synchronizedXXX`)的实现原理及使用方法,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • 本文介绍如何使用 Android 的 Canvas 和 View 组件创建一个简单的绘图板应用程序,支持触摸绘画和保存图片功能。 ... [详细]
  • 本文详细解析了Java中hashCode()和equals()方法的实现原理及其在哈希表结构中的应用,探讨了两者之间的关系及其实现时需要注意的问题。 ... [详细]
  • 并发编程 12—— 任务取消与关闭 之 shutdownNow 的局限性
    Java并发编程实践目录并发编程01——ThreadLocal并发编程02——ConcurrentHashMap并发编程03——阻塞队列和生产者-消费者模式并发编程04——闭锁Co ... [详细]
  • 深入解析 Android IPC 中的 Messenger 机制
    本文详细介绍了 Android 中基于消息传递的进程间通信(IPC)机制——Messenger。通过实例和源码分析,帮助开发者更好地理解和使用这一高效的通信工具。 ... [详细]
  • ListView简单使用
    先上效果:主要实现了Listview的绑定和点击事件。项目资源结构如下:先创建一个动物类,用来装载数据:Animal类如下:packagecom.example.simplelis ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 本文详细探讨了Java中的ClassLoader类加载器的工作原理,包括其如何将class文件加载至JVM中,以及JVM启动时的动态加载策略。文章还介绍了JVM内置的三种类加载器及其工作方式,并解释了类加载器的继承关系和双亲委托机制。 ... [详细]
  • 本文详细介绍了 org.apache.commons.io.IOCase 类中的 checkCompareTo() 方法,通过多个代码示例展示其在不同场景下的使用方法。 ... [详细]
  • 深入解析Redis内存对象模型
    本文详细介绍了Redis内存对象模型的关键知识点,包括内存统计、内存分配、数据存储细节及优化策略。通过实际案例和专业分析,帮助读者全面理解Redis内存管理机制。 ... [详细]
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社区 版权所有