文档章节

更好的重写hashCode方法

XuePeng77
 XuePeng77
发布于 2018/03/19 23:23
字数 1204
阅读 3.8K
收藏 0

「深度学习福利」大神带你进阶工程师,立即查看>>>

重写hashCode的规范

每个重写equals方法的类中,也必须重写hashCode方法。

如果不覆盖hashCode,会导致无法结合基于散列的集合正常工作,例如HashMap、HashSet和Hashtable等等,换句话说,实现了对的hashCode,就可以拿对象的实例作为Hash集合的Key,下面是重写hashCode的规范:

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

相等的对象必须具有相等的散列码(hash code)。

两个不同的实例在逻辑上有可能是相等的(equals),但是hashCode方法返回的应该是两个不同的随机整数,考虑下面这个PhoneNumber类,在企图与HashMap一起使用时,将失败:

package test.ch02;

public class PhoneNumber {

    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public int getPrefix() {
        return prefix;
    }

    public int getLineNumber() {
        return lineNumber;
    }

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

}
package test.ch02;

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        String s = m.get(new PhoneNumber(707, 867, 5309));
        System.out.println(s); // null
    }

}

由于PhoneNumber没有重写hashCode方法,从而导致两个相等的实例具有不相等的散列码。

修正这个问题很简单,只需要为PhoneNumber提供一个适当的hashCode即可。

如何重写规范的hashCode方法

一个好的hashCode方法倾向于“为不相等的对象产生不相等的散列码”。

理想情况下,散列函数应该把集合中不相等的实例均匀地分布到所有可能的散列值上,可以采用如下做法:

  1. 把某个非零的常数值,比如17,保存在一个result的int类型变量中;
  2. 对于对象中每个关键域f,完成以下步骤:
  • 为该域计算int类型的散列码c:
  • 如果该域是boolean,则计算(f ? 1 : 0);
  • 如果该域是byte、char、short或者int,则计算(int)f;
  • 如果该域是long,则计算(int)(f^(f>>>32));
  • 如果该域是float,则计算Float.floatToIntBits(f);
  • 如果该域是double,则计算Double.doubleToLongBits(f),在根据long计算;
  • 如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode;
  • 如果该域是一个数组,则要把每一个元素当作单独的域来处理,也可以使用Arrays.hashCode方法;
  • 按照result = 31 * result + c来计算散列码;

为PhoneNumber重写一个hashCode:

package test.ch02;

public class PhoneNumber {

    private final int areaCode;
    private final int prefix;
    private final int lineNumber;

    public PhoneNumber(int areaCode, int prefix, int lineNumber) {
        this.areaCode = areaCode;
        this.prefix = prefix;
        this.lineNumber = lineNumber;
    }

    public int getAreaCode() {
        return areaCode;
    }

    public int getPrefix() {
        return prefix;
    }

    public int getLineNumber() {
        return lineNumber;
    }

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

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

}
package test.ch02;

import java.util.HashMap;
import java.util.Map;

public class Test {

    public static void main(String[] args) {
        Map<PhoneNumber, String> m = new HashMap<>();
        m.put(new PhoneNumber(707, 867, 5309), "Jenny");
        String s = m.get(new PhoneNumber(707, 867, 5309));
        System.out.println(s); // Jenny
    }

}

优化hashCode方法

  • 在散列码的计算过程中,可以把冗余域(redundant field)排除在外。
  • 为什么选择31做散列码,是因为它是一个奇素数。31有个很好的特性,即用位移法和减法来代替乘法,可以得到更好的性能: 31 * i = (i<<5)-i。现代的VM可以自动完成这种优化;
  • 如果一个类是不可变的,并且计算散列码的开销很大,就应该考虑把散列码缓存在对象内部。或者考虑延迟初始化散列码,在第一次调用hashCode时缓存到内部;
  • 不要视图从散列码计算中排除掉一个对象的关键部分来提高性能;
XuePeng77
粉丝 56
博文 195
码字总数 275416
作品 0
丰台
私信 提问
加载中
请先登录后再评论。
简单CMS

主要修改: 1)增加文章模块,文章列表显示在首页和单品页中; 2)增加店铺模块,店铺显示在首页和瀑布流页中; 3)增加网站地图模块; 4)增加sitemap模块; 5)增加第三方淘宝登录功能; ...

简单CMS
2012/12/25
4.3K
0
建站引擎--PHPMyWind

PHPMyWind 是基于PHP+MySQL开发符合W3C标准的建站引擎。它将带给人们一系列高效的,成熟的企业网站建设解决方案,让您的信息以更健康的形式高速传递给需要的它的人们,同时让您感受通过PHPMy...

匿名
2013/01/14
4.4K
1
vss2svn2git

这个程序导入Visual SourceSafe(VSS)库到一个git存储库。 这是一个分叉来自vss2svn。再一次,我需要一些方法来从一个旧的VSS 6.0数据库中提取历史,而vss2git做这个事不正确。vss2svn预编译的...

匿名
2013/05/17
626
0
PocketSVG

直接根据SVG生成CGPath/UIBezierPath。 使用场景: 1. 重写UIView的时候,直接从SVG文件获取CGPath进行绘制。 2. 替代庞大的png/jpg等图形文件,节约空间和内存。 3. 可以任意改写图形的Str...

匿名
2013/05/29
1.2K
0
数据库结构比较工具--dbcompare

数据库结构比较工具,目前支持mysql,oracle,sql server. 概述: 在设计开发过程中经常会出现开发库与测试库不一致,测试库与生产库不一致,每次手工比对是个辛苦的活。 以前用java写过一个数...

dbcompare
2013/06/25
5.8K
0

没有更多内容

加载失败,请刷新页面

加载更多

旋转子段 (思维stl)

题目: 大概意思就是给你一个序列,你可以选择一段区间使它左右翻折一遍,然后呢,从1到n找一遍,看a[i]==i的数最多是多少。 其实刚才我已经把暴力思路说出来了,枚举每一个区间长度,枚举每...

osc_npw5uz1o
6分钟前
0
0
回忆录

前言? 果然退役的蒟蒻不仅没有留下有价值的学习资料,甚至连能看的颓废资料都没有。 其实这一年时间里一直想写一篇像样的回忆录。 想把高三也写进去?现在高三结束了。没时间写?现在有了。...

osc_z9ptnny9
7分钟前
0
0
mysql启动失败,unit not found

1 mysql启动 Failed to start mysqld.service: Unit not found. 2 查询/etc/init.d/下是否存在mysqld ll /etc/init.d/ | grep mysqld 发现该目录下并没有mysqld的文件,若存在,请备份一下 ...

osc_um3gbrdm
9分钟前
5
0
域名解析到底应该肿么破——详解域名解析类型

原文地址:https://www.wjcms.net/archives/%E5%9F%9F%E5%90%8D%E8%A7%A3%E6%9E%90%E5%88%B0%E5%BA%95%E5%BA%94%E8%AF%A5%E8%82%BF%E4%B9%88%E7%A0%B4%E8%AF%A6%E8%A7%A3%E5%9F%9F%E5%90%8D%......

神兵小将
9分钟前
0
0
Java并发编程:volatile关键字解析

Java并发编程:volatile关键字解析    volatile这个关键字可能很多朋友都听说过,或许也都用过。在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果。在...

osc_3r4js8qy
11分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部