在 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 件事

    (点击上方公众号,可快速关注) 原文:eclipsesource 译文:ImportNew - 南半球 链接:http://www.importnew.com/16517.html 在 Java 中, ...

  2. 【java基础学习-2--】关于Hashcode()的使用

    摘要 Java中equals()和hashCode()有一个契约: 如果两个对象相等的话,它们的hash code必须相等: 但如果两个对象的hash code相等的话,这两个对象不一定相等; 这个约 ...

  3. 半夜思考, 为什么建议重写 equals() 方法时, 也要重写 hashCode() 方法

    我说的半夜, 并不是真正的半夜, 指的是在我一个人的时候, 我会去思考一些奇怪的问题. 要理解 hashCode() 需要理解下面三个点: hash契约 哈希冲突 哈希可变 第一点: hash 契约指 ...

  4. JAVA中的各种 哈希码(HashCode) 与 equals方法在HIBERNATE的实际应用[转载]

    1.什么是哈希码(HashCode) 在Java中,哈希码代表对象的特征.例如对象 Java代码 String str1 = “aa”, str1.hashCode= 3104 String str2 ...

  5. java 中hashcode和equals 总结

    一.概述            在Java中hashCode的实现总是伴随着equals,他们是紧密配合的,你要是自己设计了其中一个,就要设计另外一个.当然在多数情况下,这两个方法是不用我们考虑的,直 ...

  6. javascript 比较对象(hashcode)

    javascript 对象的比较是比较坑爹的一件事,因为javascript对象比较的是引用地址!当两个内容完全一样的对象比较: var object1={ name:"1234 " ...

  7. hashCode()方法和equal()方法的区别

    本文参考地址:http://www.cnblogs.com/zgq0/p/9000801.html hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等 ...

  8. hashcode() equals()

     介绍一. hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode()呢? ...

  9. hashcode()和equals()的作用、区别、联系

        介绍一. hashCode()方法和equal()方法的作用其实一样,在Java里都是用来对比两个对象是否相等一致,那么equal()既然已经能实现对比的功能了,为什么还要hashCode() ...

随机推荐

  1. Java-->实现断点续传(下载)

    --> 断点续传: 就像迅雷下载文件一样,停止下载或关闭程序,下次下载时是从上次下载的地方开始继续进行,而不是重头开始... --> RandomAccessFile --> poi ...

  2. Map/Reduce的类体系架构

    Map/Reduce的类体系架构 Map/Reduce案例解析: 先以简单的WordCount例程, 来讲解如何去描述Map/Reduce任务. public static void main(Str ...

  3. 安装CDH4 (Cloudera Distribution Hadoop)步骤

    安装流程 机器和系统 3台服务器,安装centos 6.4 64bit系统,内存8G,磁盘60G,cpu单核 已配置好静态ip,并配置好/etc/hosts 下载cdh4版本 https://www. ...

  4. poj 1260 dp

    Description In Pearlania everybody is fond of pearls. One company, called The Royal Pearl, produces ...

  5. Codeforces Round #138 (Div. 2)

    A. Parallelepiped 枚举其中一边,计算其他两条边. B. Array 模拟. C. Bracket Sequence 栈. D. Two Strings \(pre[i]\)表示第i个 ...

  6. Android多线程入门学习

    (1)进程间通信交换信息的一种方式--使用handler: (2)在主线程中new一个Handler对象,并重写他的handlerMessage(Message msg)方法: (3)Message中 ...

  7. 关闭V-Ray warning消息框

    有时候模型使用低版本VR保存的,再次打开模型时会弹出V-Ray warning提示框 这个问题困扰了我一周时间.... 查了VR官方帮助文档 解决方法如下 setVRaySilentMode() -- ...

  8. LESS中文版函数手册

    LESS是一种由Alexis Sellier设计的动态层叠样式表语言.LESS 做为 CSS 的一种形式的扩展,它并没有减少 CSS 的功能,而是在现有的 CSS 语法之上,添加了许多其它的功能. 在 ...

  9. Java——异常

     /* * 异常:   是在运行时期  发生的 不正常情况. *  在java中类的形式对不正常情况进行了描述和封装对象. * * 描述不正常的情况类,就成为异常. * * 问题很多,就意味着描述 ...

  10. excel动态去重和动态排序

    其实去重和排序的方法很多,没有哪一种更好,实时去重总会省一些时间,刚好也练习了下数组公式 动态去重: =IF(ROW()<=COUNTA(员工基础数据!H:H),INDEX(员工基础数据!H:H ...