(点击上方公众号,可快速关注)

原文:eclipsesource

译文:ImportNew - 南半球

链接:http://www.importnew.com/16517.html

在 Java 中,每一个对象都有一个容易理解但是仍然有时候被遗忘或者被误用的 hashCode 方法。这里有3件事情要时刻牢记以避免常见的陷阱。

一个对象的哈希码允许算法和数据结构将对象放入隔间,就象打印机类型案件中的字母类型。打印机将所有的“A”类型放到一个房间,它寻找这个“A”的时候就只需要在这个房间进行寻找。这种简单的系统让他在未排序的抽屉中寻找类型的时候更快。这也是基于哈希的集合的想法,例如 HashMap 和 HashSet。

为了使你的类与其他基于哈希的集合或其他依赖哈希码的算法一起正常工作,所有 hashCode 的实现必须遵守一个简单的契约。

hashCode 契约

这个契约在 hashCode 方法的 JavaDoc 中进行了阐述。它可以大致的归纳为下面几点:

  1. 在一个运行的进程中,相等的对象必须要有相同的哈希码

  2. 请注意这并不意味着以下常见的误解:

  3. 不相等的对象一定有着不同的哈希码——错!

  4. 有同一个哈希值的对象一定相等——错!

这个契约允许不同的对象共享相同的哈希码,例如根据上图中的的描述,“A”和“μ”对象的哈希值就一样。在数学术语中,从对象到哈希码的映射不一定为内射或者双射。这是显而易见的,因为可能的不同对象的数量经常比可能的哈希吗的数量 (2^32)更大。

编辑:在早期的版本中,我错误的认为哈希码的映射一定属于内射,但是不一定是双射,这显然是错的。感谢 Lucian 指出这个错误。

这个约定直接导致了第一个规则:

1. 无论你何时实现 equals 方法,你必须同时实现 hashCode 方法

如果你不这样做,你将会带来损坏的对象。为什么?一个对象的 hashCode 方法需要与 equals 方法考虑同样的域。通过重写 equals 方法,你将申明一些对象与其他对象相等,但是原始的 hashCode 方法将所有的对象看做是不同的。所以你将会有不同哈希码的相同对象。例如,在 HashMap 中调用 contains 方法将会返回 false,即使这个对象已经被添加。

怎样写一个好的 hashCode 方法不在这篇文章的范围内,在 Joshua Bloch 很受欢迎的书《Effective Java》中被很好的阐释,Java 开发人员的书架上不应缺少这本书。

【你的项目需要专业意见吗?我们的 Developer Support 会为你解决问题。|在我们的 Software Craftsmanship 页面上寻找关于怎样编写简洁代码的更多提示。】

为了安全起见,让 Eclipse IDE 一次产生 equals 和 hashCode 方法: Source > Generate hashCode() and equals()….

为了保护你自己,你还可以配置 Eclipse 来检测实现了 equals 方法但是没有实现 hashCode 方法的类,并显示错误。不幸的是,此选项默认是指为“忽略”:Preferences > Java >Compiler > Errors/Warnings,然后用快速筛选器来搜索“hashcode”:

更新:正如 laurent 指出,equalsverifier 是一个强大的工具,它用来验证 hashCode 和 equals 方法的约定。您应该考虑在您的单元测试中使用它。

哈希码冲突

任何时候,两个不同对象有相同的哈希码,我们称之为冲突。冲突不要紧,它只是意味着有多个对象在同一个空间里,所以 HashMap 会再检查一遍来找正确的对象。大量的冲突将会降低系统的性能,但是它们不会导致错误的结果。

但是如果你误认为哈希码是一个对象唯一的句柄,例如使用它作为Map的key,你有时会得到错误的对象。因为虽然冲突很罕见,但他们是不可避免的。例如,字符“Aa”和“BB”产生相同的哈希码:2112。因此:

2. 永远不要把哈希码误用作一个key

你可能会反对,不像打印机的类型例子,在 Java 中,有 4,294,967,296 的空间(2^32 个可能的整型值)。40亿的插槽,发生冲突似乎是极不可能的对吗?

事实证明它不是不太可能。这是令人惊讶的冲突:请想象一下在一个房间里有 23 个随机的人。你觉得两个人是同一天生日的几率有多大 ?很低,因为一年有 365 天吗?事实上,几率是 50% 左右!50 个人是保守的估计。这个现象称为生日悖论。应用到哈希码,这意味着在 77163 个不同的对象中,有 50% 的可能性发生冲突–假设你有一个理想的哈希的函数,均匀地把对象分布在所有可用的空间里面。

例如:

安然公司的电子邮件集包含 520,924 封电子邮件。计算电子邮件内容字符串的哈希码时,我发现 50 对(甚至是 2 个三元组)不同的电子邮件有着相同的哈希码。对于五十万个字符串,这是一个很好的结果。但是这里的信息是:如果你有很多数据元素,冲突就会发生。如果你正在使用哈希码作为 key,你不会立即注意到你的错误。但是少数人会收到错误的邮件。

哈希码可变

最后,在哈希码的契约中,有一个很重要的细节是相当让人吃惊的:hashCode 并不保证在不同的应用执行中得到相同的结果。让我们看一看 Java 文档:

在一次 Java 应用的执行中,对于同一个对象,hashCode 方法必须始终返回相同的整数,但这整数不反映对象是否被修改(equals 比较)的信息。同一个应用的不同执行,该整数不必保持一致。

事实上,这是不常见的,一些类库中的类甚至指定它们用于计算哈希码的精确公式(例如字符串)。对于这些类,哈希码总是会相同。虽然大部分的哈希码的实现提供稳定的值,但你不能依赖于这一点。正如这篇文章指出的,有些类库在不同进程中会返回不同的哈希值,这有的时候会让人困惑。谷歌的 Protocol Buffers 就是一个例子。

因此,你不应该在分布式应用程序中使用哈希码。一个远程对象可能与本地对象有不同的哈希码,即使这两个对象是相等的。

3. 在分布式应用中不要使用哈希码

此外,你应该意识到从一个版本到另一个版本哈希码的功能实现可能会更改。因此您的代码不应该依赖于任何特定的哈希码值。例如,你不应该使用哈希码来持久化状态。下次你运行程序的时候,“相同”对象的哈希码可能不同。

最好的建议可能是:完全不使用哈希码,除非你自己创造了基于哈希的算法。

一种替代方法:SHA1

你可能知道加密的哈希码 SHA1 有时被用来标识对象(例如,git这样做)。这也是不安全吗?不。SHA1 使用 160 位密钥,这使得冲突几乎是不可能的。即使有很多对象,在这个空间发生冲突的几率远远低于一颗流星撞到你正在执行程序的电脑的几率。这篇文章对冲突的概率作了很好的概述。

关于 hashCode() 你需要了解的 3 件事的更多相关文章

  1. 【转】关于 hashCode() 你需要了解的 3 件事

    在 Java 中,每一个对象都有一个容易理解但是仍然有时候被遗忘或者被误用的 hashCode 方法.这里有3件事情要时刻牢记以避免常见的陷阱. 一个对象的哈希码允许算法和数据结构将对象放入隔间,就象 ...

  2. 关于 Java Collections API 您不知道的 5 件事,第 1 部分

    定制和扩展 Java Collections Java™ Collections API 远不止是数组的替代品,虽然一开始这样用也不错.Ted Neward 提供了关于用 Collections 做更 ...

  3. 关于 Java Collections API 您不知道的 5 件事--转

    第 1 部分 http://www.ibm.com/developerworks/cn/java/j-5things2.html 对于很多 Java 开发人员来说,Java Collections A ...

  4. 做一个 App 前需要考虑的几件事

    做一个 App 前需要考虑的几件事  来源:limboy的博客   随着工具链的完善,语言的升级以及各种优质教程的涌现,做一个 App 的成本也越来越低了.尽管如此,有些事情最好前期就做起来,避免当 ...

  5. 【转载】在IT界取得成功应该知道的10件事

     在IT界取得成功应该知道的10件事 2011-08-11 13:31:30 分类: 项目管理 导读:前面大多数文章都是Jack Wallen写的,这是他的新作,看来要成为NB程序员还要不停的自我总结 ...

  6. 安装完CentOS 7 后必做的七件事

    CentOS是最多人用来运行服务器的 Linux 版本,最新版本是 CentOS 7.当你兴趣勃勃地在一台主机或 VPS 上安装 CentOS 7 后,首要的工作肯定是加强它的安全性,以下列出的七件事 ...

  7. A/B 测试之前必须要了解的 10 件事

    如今,转化率优化(CRO)已是营销人员必须具备的技能,并且与 ROI 直接挂钩.但是在优化网页的转化率方面又有太多因素要考量,如果你已经不堪其忧,请专心做一件事-- A/B 测试. A/B测试,即你设 ...

  8. 关于Promise:你可能不知道的6件事

    FROM ME : 文章介绍了6个Promise的知识点: 1.then() 返回一个 forked Promise(分叉的 Promise):返回的有两种情况: 2.回调函数应该传递结果:在 pro ...

  9. Ubuntu 16.04 LTS安装好需要设置的15件事(喜欢新版本)

    看到这篇文章说明你已经从老版本升级到 Ubuntu 16.04 或进行了全新安装,在安装好 Ubuntu 16.04 LTS 之后建议大家先做如下 15 件事.无论你是刚加入 Ubuntu 行列的新用 ...

随机推荐

  1. iOS 开发中使用 NSURLProtocol 拦截 HTTP 请求

    这篇文章会提供一种在 Cocoa 层拦截所有 HTTP 请求的方法,其实标题已经说明了拦截 HTTP 请求需要的了解的就是 NSURLProtocol. 由于文章的内容较长,会分成两部分,这篇文章介绍 ...

  2. 考试宝典-真题园安卓AppV2.1.0新版发布啦,全新界面,全新体验,全面适配Android 5.0&6.0系统!

    真题园移动客户端是真题园网 http://www.zhentiyuan.com 旗下的一款学习考试应用App. 1.全新适配Android5.0.6.0系统,重新优化架构网络通信模块. 2.全新清爽U ...

  3. 自定义控件(视图)1期笔记02:View的绘制流程

    1. 引言: 来自源码的3个方法: (1)public final void measure():测量,用来控制控件的大小,final不建议覆写 (2)public void layout():布局, ...

  4. Java基础知识强化之多线程笔记04:并行和并发 区别

    1. 并发 和 并行区别 (1)并发:(单核) 并发,是在同一个cpu上同时(不是真正的同时,而是看来是同时,因为cpu要在多个程序间切换)运行多个程序. 并发是指两个任务都请求运行,而处理器只能按受 ...

  5. mysql 数据备份还原

    悲剧的一天,不小心将数据库删了... 命令行备份数据库 1.mysqldump命令进行备份.该命令将连接MySQL服务器并创建SQL转储文件,该文件包含了重新创建数据库所必需的所有SQL语句.该命令的 ...

  6. 关于Git的stash命令

    add 添加新文件到 Git 代码仓库的索引中 $ git add filename mv 移动或重命名文件 $ git mv old-filename new-filename rm 从工作目录和 ...

  7. JS 乱记

    JS 中不存在块级作用域,也就是说在全局作用域下 if ,for 语句中用 var 声明的变量是全局变量. JS 中浮点数运算的值为近似值,比如:0.1 + 0.2 不等于 0.3 ,所以避免使用浮点 ...

  8. ServletContext中的转发

    客户端向服务器发送请求,服务器将请求进行转发,获得响应信息,客户端只发送一次请求,地址栏信息不变. 服务器接收类,进行转发 package com.itheima.zhuanfa; import ja ...

  9. JSP编程中常用的JavaScript技术(转载)

    1.<tronMouseOver=this.style.backgroundColor=’#FFFFFF’ onMouseOut=this.style.backgroundColor=”> ...

  10. struts2 package元素配置(转载)

    package 元素的所有属性及对应功能: Attribute Required Description name yes key to for other packages to reference ...