本文参考

本篇文章参考自《Effective Java》第三版第十一条"Always override hashCode when you override equals"

You must override hashCode in every class that overrides equals

hashCode()方法的通用约定如下:

  • When the hashCode method is invoked on an object repeatedly during an execution of an application, it must consistently return the same value, provided no information used in equals comparisons is modified. This value need not remain consistent from one execution of an application to another
  • If two objects are equal according to the equals(Object) method, then calling hashCode on the two objects must produce the same integer result
  • If two objects are unequal according to the equals(Object) method, it is not required that calling hashCode on each of the objects must produce distinct results. However, the programmer should be aware that producing distinct results for unequal objects may improve the performance of hash tables

若未重载hashCode()方法,只是继承自Object,即使"值类"的实例"逻辑相等",也会在HashMap和HashSet集合中表现出不同,下面是原文的例子:

Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");

若PhoneNumber类未重载hashCode()方法,便无法使用m.get()方法获取键new PhoneNumber(707, 867, 5309)对应的值"Jenny",因为查找的过程不仅仅只是通过equals()方法比较与某个键是否相等,还比较了各自的散列码,而Object的hashCode()方法只是将实例的内存地址转化为散列码

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 Java programming language.)

a hash function should distribute any reasonable collection of unequal instances uniformly across all int values

下面是一种解决方法:

  • Declare an int variable named result, and initialize it to the hash code c for the first significant field in your object( a significant field is a field that affects equals comparisons, and you must exclude any fields that are not used in equals comparisons)

Do not be tempted to exclude significant fields from the hash code computation to improve performance

  • For every remaining significant field f in your object, do the following:
    • If the field is of a primitive type, compute Type.hashCode(f), where Type is the boxed primitive class corresponding to f's type
    • If the field is an object reference and this class's equals method compares the field by recursively invoking equals, recursively invoke hashCode on the field. If a more complex comparison is required, compute a "canonical representation"(范式) for this field and invoke hashCode on the canonical representation. If the value of the field is null, use 0 (or some other constant, but 0 is traditional)
    • If the field is an array, treat it as if each significant element were a separate field. That is, compute a hash code for each significant element by applying these rules recursively, and combine the values. If the array has no significant elements, use a constant, preferably not 0. If all elements are significant, use Arrays.hashCode
    • Combine the hash code c
      result = 31 * result + c; (The advantage of using a prime is less clear, but it is traditional)
  • Return result

下面是采用上述规则的hashCode()重载示例

private String reference;
private int[] array;
private int primitive;
@Override
public int hashCode() {

  // Declare an int variable named result
  // and initialize it to the first significant field in your object

  int
result = reference.hashCode();

  // If all elements are significant, use Arrays.hashCode
  // Combine the hash code c

  result = 31 * result + Arrays.hashCode(array);

  // If the field is of a primitive type, compute Type.hashCode(f)

  result = 31 * result + Integer.hashCode(primitive);

  return result;
}

另外还有一种简单的生成散列码的方式 —— Objects.hash(),但是它会引发数组的创建,以便传入数目可变的参数, 如果参数中有基本类型,还需要装箱和拆箱,建议只将这类散列函数用于不太注重性能的情况

在前面一篇关于"AutoValue"的文章中,采用的是另外一套生成hashCode()方法的规则

cache the hash code in the object

如果是一个不可变的类(如"值类"),并且计算散列码开销较大(如字段较多的情况),可以将散列码作为单例缓存在对象内部,视情况采用"懒汉式"还是"饿汉式",下面代码采用"懒汉式"加载

private int hashCode;

@Override
public int hashCode() {

  int result = hashCode;

  if (result != 0) {

    result = reference.hashCode();

    result = 31 * result + Arrays.hashCode(array);

    result = 31 * result + Integer.hashCode(primitive);
  }

  return result;
}

Effective Java —— 覆盖equals时总要覆盖hashCode的更多相关文章

  1. Item 9 覆盖equals时总要覆盖hashCode

    为什么覆盖equals时,总要覆盖hashCode?   原因是,根据Object规范: 如果两个对象根据equals(Object)方法比较是相等的,那么调用这两个对象中任意一个对象的hashCod ...

  2. 覆盖equals时总要覆盖hashCode

    本文涉及到的概念 1.为什么重载equals方法时,要重载hashCode函数;没有重载hashCode带来的问题 2.一个对象hashCode的生成规则       1.为什么重载equals方法时 ...

  3. 第9条:覆盖equals时总要覆盖hashCode

    在每个覆盖equals方法的类中,也必须覆盖hashCode方法.否则,会违反Object.hashCode的通用约定,从而导致该类无法结合所有基于散列的集合一起正常工作,包括HashMap,Hash ...

  4. 【Effective Java】5、覆盖equals时总要覆盖hashcode

    package cn.xf.cp.ch02.item9; import java.util.HashMap; import java.util.Map; public class PhoneNumbe ...

  5. 覆盖equals 时总要覆盖hashCode(9)

    2019独角兽企业重金招聘Python工程师标准>>> 1.在每个覆盖了equals 方法的类中,也必须覆盖hashCode 这是关于hashCode 的通用约定 这样可以与 基于散 ...

  6. 覆盖equals()时总要覆盖hashCode()

    覆写如下: public class User{ private Integer id; private String userName; private String passWord; publi ...

  7. EffectiveJava(9)覆盖equals是总要覆盖hashCode

    覆盖equals是总要覆盖hashCode 通过散列函数将集合中不相等的实例均匀的分布在所有可能的散列值上 1.把某个非零的常数值保存在一个名为result的int类型变量中 2.对于对象中每个关键域 ...

  8. Item 8 覆盖equals时请遵守通用约定

    在覆盖equals方法的时候,你必须要遵守它的通用约定,不遵守,写出来的方法,会出现逻辑错误.下面是约定的内容:   equals方法实现了等价关系:   自反性.对于任何非null的引用值,x.eq ...

  9. 第8条:覆盖equals时遵守通用约定

    如果不需要覆盖equals方法,那么就无需担心覆盖equals方法导致的错误. 什么时候不需要覆盖equals方法? 1.类的每个实例本质上是唯一的. 例如对于Thread,Object提供的equa ...

随机推荐

  1. 用 UI 多线程处理 WPF 大量渲染的解决方案

    众所周知, WPF 的 UI 渲染是单线程的,所以如果我们异步或者新建线程去进行数据处理的时候,处理完,想要更新 UI 的时候,需要调用一下 Dispatcher.Invoke,将处理完的数据推入到 ...

  2. Service层抽象规范

    Service层是整个web系统的负责业务逻辑一块,最有必要实现抽象,Service层要达到复用性,低耦合性.那么该如何抽象呢?一般遵循以下原则 1.单一职责(SRP) 2.开放-封闭(OCP) 3. ...

  3. Spark on Yarn出现hadoop.compression.lzo.LzoCodec not found问题发现及解决

    问题描述: spark.SparkContext: Created broadcast 0 from textFile at WordCount.scala:37 Exception in threa ...

  4. windows下cuda、cudnn以及pytorch的安装

    一.在anaconda下配置cuda.cudnn以及pytorch环境 1.打开Anaconda Prompt,输入 conda create -n pytorch python=3.8        ...

  5. JZ-071-把数字翻译成字符串

    把数字翻译成字符串 题目描述 给定一个数字,按照如下规则翻译成字符串:1 翻译成"a",2 翻译成"b"... 26 翻译成"z".一个数字 ...

  6. MRS IoTDB时序数据库的总体架构设计与实现

    MRS IoTDB时序数据库的总体架构设计与实现 MRS IoTDB是华为FusionInsight MRS大数据套件最新推出的时序数据库产品,其领先的设计理念在时序数据库领域展现出越来越强大的竞争力 ...

  7. dopamine源码解析之dqn_agent

    目录 epsilon函数 DQNAgent构造函数核心参数 DQNAgent核心函数 tf.make_template 核心数据流图 epsilon函数 linearly_decaying_epsil ...

  8. 对于处理datetime数据类型的一些常用方法:

    datetime数据类型常用方法: 在项目中从数据库中取出数据后通常需要先绘制图像进行数据的观察,此过程中使用到的方法: 1.时间数据类似于 2022-03-23 14:21:45 可以先转换为dat ...

  9. 打靶笔记-01-vulnhub-moneybox

    打靶笔记-01-vulnhub-moneybox 本篇笔记根据苑老师视频进行学习记录 https://www.bilibili.com/video/BV1Lv411n7Lq/?spm_id_from= ...

  10. [树]LeetCode589 N叉树的前序遍历

    LeetCode N叉树的前序遍历 前言:树的前中后序遍历已经是很经典的题目的,要么递归要么迭代,不过还是比较习惯于递归的写法 TITLE 给定一个 n 叉树的根节点 root ,返回 其节点值的 前 ...