第9条:覆盖equals时总要覆盖hashcode

没有梦想的小灰灰 没有梦想的小灰灰     2022-07-31     585

关键词:

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

 

hashCode约定内容:

1.只要对象equals方法的比较操作所用到的信息没有被修改,对同一对象调用多次,hashCode方法都必须返回同一整数。在同一应用程序的多次执行过程中,每次执行返回的整数可以不一致。

2.如果两个对象根据equals(Object)方法比较是相等的,那么这两个对象的hashCode返回值相同。

3.如果两个对象根据equals(Object)方法比较是不等的,那么这两个对象的hashCode返回值不一定不等,但是给不同的对象产生截然不同的整数结果,能提高散列表的性能。

 

考虑:

public class PhoneNumber {
    private final int areaCode;
    private final int prefix;
    private final int lineNumber;
    
    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        rangeCheck(areaCode, 999, "area code");
        rangeCheck(prefix, 999, "prefix");
        rangeCheck(lineNumber, 9999, "line number");
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }
    
    private static void rangeCheck(int arg, int max, String name) {
        if(arg < 0 || arg > max) {
            throw new IllegalArgumentException(name + ": " + arg);

        }
    }
    
    @Override
    public boolean equals(Object o) {
        if(o == this)
            return true;
        if(!(o instanceof PhoneNumber))
            return false;
        PhoneNumber pn = (PhoneNumber)o;
        return pn.lineNumber == lineNumber
                && pn.prefix == prefix
                && pn.areaCode == areaCode;
    }
    
}

运行下面代码:

Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(map.get(new PhoneNumber(707, 867, 5309)));

我们期望它返回Jenny,然而它返回的是null。

原因在于违反了hashCode的约定,由于PhoneNumber没有覆盖hashCode方法,导致两个相等的实例拥有不相等的散列码,put方法把电话号码对象放在一个散列桶中,get方法从另外一个散列桶中查找这个电话号码的所有者,显然是无法找到的。

只要覆盖hashCode并遵守约定,就能修正这个问题。

 

一个好的散列函数倾向于“为不相等的对象产生不相等的散列码”,下面有简单的解决办法:

1.把某个非零的常数值,如17,保存在一个名为result的int类型的变量中。(为了2.a中计算的散列值为0的初始域会影响到散列值)

2.对于对象中的每个关键域f,完成一下步骤:

 a.为该域计算int类型的散列码c

  i.如果该域是boolean,计算(f ? 1:0)

  ii.如果该域是byte、char、short或者int类型,则计算(int)f

  iii.如果该域是long,则计算(int)(f ^ (f >>> 32))

  iv.如果该域是float,则计算Float.floatToIntBits(f)

  v.如果该域是double,则计算Double.doubleToLongBits(f),然后

   vi.如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式”,然后针对这个“范式”调用hashCode。如果域的值为null,则返回0(或其他某个常数,但通常为0)。

  vii.如果该域是一个数组,则要吧每一个元素当做单独的域来处理,也就是要递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据2.b把这些散列值组合起来。如果数组域中的每个元素都很重要,可以使用1.5中增加的其中一个Array.hashCode方法。

 b.按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:

  result = 31 * result + c。(选择31是因为它是一个奇素数,如果乘数是偶数,乘法溢出时会丢失信息,VM可以优化 31 * i == (i << 5) - i)

3.返回result。

 

编写完hashCode方法后,编写单元测试来验证相同的实例是否有相等的散列码。

 

把上面的解决方法应用到PhoneNumber类中:

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + areaCode;
    result = 31 * result + prefix;
    result = 31 * result + lineNumber;
    return result;
}

现在使用之前的测试代码,发现能够返回Jenny了。

 

如果一个类是不可变的,并且计算散列码的开销很大,应该考虑把散列码缓存到对象内部而不是每次请求都重新计算散列码,如果这种类大多数对象会被用作散列键,应该在创建实例的时候计算散列码,否则可以选择延迟初始化散列码。

 

注意:不要试图从散列码计算中排除掉一个对象的关键部分来提高性能。虽然这样做运行起来可能更快,但效果不见得好,在拥有大量实例的时候,忽略的域区别仍然非常大,但散列函数仍然把它们映射到同样的散列桶中,例如Java 1.2之前实现的String散列函数至多检查16个字符,对于像URL这样的大型集合,散列函数表现出病态的行为(把第16个字符后相差非常大的URL映射到同样的散列桶中,使得碰撞率很高,性能降低)。

java对象覆盖equals时总要覆盖hashcode

  在每个覆盖了equals方法的类中,也必须覆盖hashCode方法。   hashCode的通用约定:  1在程序的执行过程中,只要对象的equals方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode方法必须始... 查看详情

覆盖equals时总要覆盖hashcode

Object条约规定:相等的对象必须具有相同的散列码hashCode假如将只覆盖equals方法,没覆盖hashCode方法的类用于HashMap中,将会出现问题,会出现get()方法返回时不是同一个对象这就相当于将对象put进一个散列桶,却在另一个散列桶ge... 查看详情

effectivejava5覆盖equals时总要覆盖hashcode

packagecn.xf.cp.ch02.item9;importjava.util.HashMap;importjava.util.Map;publicclassPhoneNumber{privatefinalshortareaCode;privatefinalshortprefix;privatefinalshortlineNumber;publicPhoneNumber(intareaCod 查看详情

effectivejava第九条:覆盖equals时总要覆盖hashcode(代码片段)

先来看一个例子:PhoneNumer类publicclassPhoneNumberprivatefinalintareaCode;privatefinalintprefix;privatefinalintlineNumber;publicPhoneNumber(intareaCode,intprefix,intlineNumber)rangeCheck(areaCode,999,&# 查看详情

java:effectivejava学习笔记之覆盖equals时总要覆盖hashcode(代码片段)

Java覆盖equals时总要覆盖hashcode覆盖equals时总要覆盖hashcode1.什么是hashcode方法?2.hashcode相等与对象相等之间的关系:(保证设计是规范的前提下)3.为什么要覆盖hashcode3.1、覆盖equals时总要覆盖hashCode3.2、如何在覆盖... 查看详情

java实战源码解析为什么覆盖equals方法时总要覆盖hashcode方法(代码片段)

1、背景知识本文代码基于jdk1.8分析,《Java编程思想》中有如下描述:另外再看下Object.java对hashCode()方法的说明:/***Returnsahashcodevaluefortheobject.Thismethodis*supportedforthebenefitofhashtablessuchasthoseprovidedby*& 查看详情

java实战源码解析为什么覆盖equals方法时总要覆盖hashcode方法(代码片段)

1、背景知识本文代码基于jdk1.8分析,《Java编程思想》中有如下描述:另外再看下Object.java对hashCode()方法的说明:/***Returnsahashcodevaluefortheobject.Thismethodis*supportedforthebenefitofhashtablessuchasthoseprovidedby*& 查看详情

effectivejava目录

...用的方法覆盖equals时请遵守通用约定覆盖equals时总要覆盖hashCode始终要覆盖toString谨慎地覆盖clone考虑实现Comparabl 查看详情

为啥 StringBuffer/StringBuilder 不覆盖 equals 或 hashCode?

】为啥StringBuffer/StringBuilder不覆盖equals或hashCode?【英文标题】:WhydoesStringBuffer/StringBuildernotoverrideequalsorhashCode?为什么StringBuffer/StringBuilder不覆盖equals或hashCode?【发布时间】:2012-06-2204:17:58【问题描述】:为什么StringBuffer/StringBu 查看详情

为啥我需要覆盖 Java 中的 equals 和 hashCode 方法?

】为啥我需要覆盖Java中的equals和hashCode方法?【英文标题】:WhydoIneedtooverridetheequalsandhashCodemethodsinJava?为什么我需要覆盖Java中的equals和hashCode方法?【发布时间】:2022-01-0303:47:55【问题描述】:最近我通读了这个DeveloperWorksDocumen... 查看详情

为啥我需要覆盖 Java 中的 equals 和 hashCode 方法?

】为啥我需要覆盖Java中的equals和hashCode方法?【英文标题】:WhydoIneedtooverridetheequalsandhashCodemethodsinJava?为什么我需要覆盖Java中的equals和hashCode方法?【发布时间】:2019-01-2205:47:01【问题描述】:最近我通读了这个DeveloperWorksDocumen... 查看详情

equals()和hashcode()必须同时覆盖的原因

...候,一种比较常见的覆盖就是覆盖Object中的equals()方法和hashCode()方法。如果不这样做的话,就很可能违反Object.hashCode()的通用约定,从而在利用自己建的类构建需要Hash化的集合的正常工作。其中有一条约定很重要:如果两个对象... 查看详情

覆盖 Java 中的 hashCode() 和 equals() 方法

】覆盖Java中的hashCode()和equals()方法【英文标题】:OverwritingthehashCode()andequals()methodinJava【发布时间】:2020-06-0912:30:51【问题描述】:正如标题所示,我的问题与重写hashCode()和equals()方法有关。但是,这也不完全正确,我只是不知... 查看详情

在 Java 中覆盖 equals 和 hashCode 时应该考虑哪些问题?

】在Java中覆盖equals和hashCode时应该考虑哪些问题?【英文标题】:WhatissuesshouldbeconsideredwhenoverridingequalsandhashCodeinJava?【发布时间】:2010-09-0620:31:11【问题描述】:覆盖equals和hashCode时必须考虑哪些问题/陷阱?【问题讨论】:【参... 查看详情

仅使用一个变量覆盖 HashCode() 的正确方法是啥?

】仅使用一个变量覆盖HashCode()的正确方法是啥?【英文标题】:WhatisthecorrectwaytooverrideHashCode()usingonlyonevariable?仅使用一个变量覆盖HashCode()的正确方法是什么?【发布时间】:2022-01-1416:32:20【问题描述】:我目前正在尝试学习如... 查看详情

hashcode与equals的区别与联系

...equals方法用于比较对象的内容是否相等(覆盖以后)2、hashcode方法只有在集合中用到3、当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。4、将对象放入到集合中时,首先... 查看详情

intellijidea中怎么覆盖tostring(),hashcode(),equals()?

...ject类的子类。这些类都或继承或覆盖了Object类中的equal,hashCode和toString方法。在自定义类的时候,为了实现比较、在集合中查找、显示类信息等功能,也需要用到这些方法,默认情况下这些方法都继承自Object,但有的时候需要... 查看详情

hashset源码分析(代码片段)

...储键值对,Hashset仅存储对象。HashSet使用成员对象来计算hashcode值。2.原理在《Headfistjava》一书中有描述:当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比... 查看详情