前言

前面两节内容我们详细讲解了Hashtable算法和源码分析,针对散列函数始终逃脱不掉hashCode的计算,本节我们将详细分析hashCode和equals,同时您将会看到本节内容是从《Effective Java》学习整理而来(吐槽一句,这本书中文版翻译的真垃圾),对于《Effective Java》这本书很有学习价值,但是我不会像其他童鞋一样,直接从这本书讲解一个系列,我所采用的是学习到对应地方然后参考不同java经典书籍进行总结,循序渐进式这样效果更佳,好了,我们开始吧。

equals

翻看《Effective Java》关于equals这一节内容,直接抛出重写equals必须遵守的如下五大约定,当我看到这几大特性时,顿时惊呆了,这不就是大学线代讲解矩阵时的特点么,学以致用原来是这么个道理。

1、自反性:对于非空的对象x,x.equals(x)必须返回true.

2、对称性:对于非空的对象x和y,若x.equals(y)等于true时,那么y.equals(x)也必须返回true.

3、传递性:对于非空的对象x、y和z,如果x.equals(y)和y.equals(z)等于true时,那么x.equals(z)也必须返回true

4、一致性:对于非空的对象x和y,如果利用equals判断对象的信息没有被修改时,无论调用多少次,那么x.equals(y)要么为true,要么为false

5、对于非空的对象x,x.equals(null)必须返回false

关于第一点很好理解,非空对象自身引用必须相等,对于第二点书中所给的例子则是将重写对象比较某个字符串时不区分大小写,但是字符串对象是区分大小写,如此这样将导致对称不一致问题,对于第三点则是继承时注意equals的传递性,第4点则强调多次调用通过equals判断的恒等性,最后一点更好理解如若不判断则会抛出空指针异常。那么我们实际在重写equals时可将以下几点作为模板来使用就可以啦。

1、使用“==”判断两个对象是否引用相同

2、使用instanceof操作符来检查参数类型是否相同

3、若类型相同,则将参数转换为正确的类型

4、比较对象中每个值是否都相等,若全部相等则返回true,否则为false

如上几点模板来自《Effective Java》对重写equals的总结,当然我们可以从重写字符串对象中的equals找到如上影子,字符串对象的equals方法如下:

    public boolean equals(Object anObject) {
// 判断对象引用是否相等,相等直接返回
if (this == anObject) {
return true;
}
//判断对象参数类型是否正确
if (anObject instanceof String) { //若参数类型相同,则转换为对应的参数类型
String anotherString = (String)anObject;
int n = value.length; //比较参数对象中的所有值是否相等
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = ;
while (n-- != ) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}

好了,到这里我们讲解完了equals,还是比较简单,那么重写equals时为何一定要重写hashCode呢?主要原因在于:这是通用约定,如果是基于散列的集合比较HashMap或者HashSet等,存储对象地址需要通过散列函数计算hashCode,如若不这样做将会出现意想不到的问题。那么意想不到的问题是什么呢?

hashCode

下面我们用一个例子来讲解为何重写equals时一定要重写hashCode。

public class Person {
int age;
String name; public Person(int age, String name) {
this.age = age;
this.name = name;
} @Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} if (obj instanceof Person) {
Person p = (Person) obj;
return (this.age == p.age && this.name == p.name);
} return false;
}
}

如上我们给出一个Person对象,然后带有年龄和名称两个属性,重写时判断年龄和名称相等即可认为为同一人,下面我们在控制台进行如下操作,然后我们看看将会打印出什么结果呢。

        Person p1 = new Person(12, "Jeffcky");
Person p2 = new Person(12, "Jeffcky"); Hashtable hashtable = new Hashtable();
hashtable.put(p1, "v1"); System.out.println(hashtable.get(p2));

不难理解,因为Hashtable对象存储地址是基于hashCode,但是上述我们没有重写hashCode,所以我们实例化对象p2时,即使重写了equals两个对象相等,结果获取p2的值肯定是获取不到的,因为hashCode不等,接下来我们重写hashCode

   @Override
public int hashCode() {
return (31 * Integer.valueOf(this.age).hashCode() + name.hashCode());
}

我们看到字符串对象重写了hashCode,因为字符串用的很频繁,同时我们极有可能在散列集合中用到。下面我们来看看字符串对象的hashCode实现方式。

上图标记出的就是计算字符串的hashCode核心即散列函数,从上看出通过字符串中每一个字符的ASCII码来计算,同时我们也可再拓展下看源码数值类型的hashCode就是其本身。上述计算方式最终我们数学进行归纳出计算方法为:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

比如我们计算字符串【AC】的hashCode,根据如上计算公式则是

65*31^(2-1) + 67*31^(2-2) = 2082

在《Effective Java》中提到之所以选择31的原因是:它是一个奇素数,如果乘数是偶数,并且乘法溢出的话,信息就会丢失,因为2相乘等价于移位运算。使用素数的好处并不很明显,但是习惯使用素数来计算散列结果。我严重怀疑是不是翻译的人理解错了意思,对于书中给出选择素数的原因无法让人折服,这里我来讲解我个人的想法。

散列函数为什么要使用质数

选择31的原因是因为它是质数(素数),而不是因为它是奇数。当我们插入一个元素到哈希表中时,哈希如何识别需要将元素存储在哪个存储桶中(Bucket)呢?这是一个重要的问题,使得强制性要求哈希能够在恒定时间内告诉我们将值存储在哪个存储桶中,以便能够快速检索。我们能想到的是傻瓜式操作方式即循环遍历比较,这种顺序搜索将直接导致哈希性能恶化,直接取决哈希表所包含值的数量。换句话说,这将具有线性性能成本(O(N)),随着键(N)的数量越来越大,性能可想而知。另一个复杂之处是我们要处理的值的实际类型。若我们要处理字符串和其他复杂类型,检查或比较本身的数量将导致成本又将变得很高。基于以上叙述,所以我们至少需要解决两个问题,其一是便于快速检索而非顺序检索,其二是解决复杂类型值的比较。解决此问题的简单方法是希望出现一种将复杂值分解为易于使用的键或哈希的方法,实现此过程的最简单方法是生成唯一编号,该数字必须是唯一的,因为我们要区分一个值和另一个值。质数是唯一数字,它们的独特之处在于,由于使用了素数来构成素数,因此素数与任何其他数字的乘积具有的最大可能的唯一性(不像素数本身那样唯一),质数的此属性在哈希函数中使用可减少冲突次数(或碰撞)。例如使用4 * 8,则它比诸如3 * 5的质数乘积更有可能发生冲突,32可以通过1 * 32或2 * 16或4 * 8或2 ^ 5等计算得到,但3*5 只能以1 * 15或3 * 5得到15。

总结

本文我们详细讨论了hashCode和equals,以及分析了在散列函数中使用质数的原因,这里还存在一节内容留到学习虚拟机时再补上,通过分析虚拟机源码了解hashCode具体实现,下一节我们将进入学习分析HashMap源码,感谢您的阅读,我们下节见。

Java入门系列之hashCode和equals(十二)的更多相关文章

  1. Java 集合系列 14 hashCode

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  2. 《手把手教你》系列技巧篇(三十二)-java+ selenium自动化测试-select 下拉框(详解教程)

    1.简介 在实际自动化测试过程中,我们也避免不了会遇到下拉选择的测试,因此宏哥在这里直接分享和介绍一下,希望小伙伴或者童鞋们在以后工作中遇到可以有所帮助. 2.select 下拉框 2.1Select ...

  3. 《手把手教你》系列技巧篇(五十二)-java+ selenium自动化测试-处理面包屑(详细教程)

    1.简介 面包屑(Breadcrumb),又称面包屑导航(BreadcrumbNavigation)这个概念来自童话故事"汉赛尔和格莱特",当汉赛尔和格莱特穿过森林时,不小心迷路了 ...

  4. Java 中正确使用 hashCode 和 equals 方法

    在这篇文章中,我将告诉大家我对hashCode和equals方法的理解.我将讨论他们的默认实现,以及如何正确的重写他们.我也将使用Apache Commons提供的工具包做一个实现. 目录: hash ...

  5. Java中正确使用hashCode() 和equals() 、==

    在java编程中,经常会遇到两个对象中某个属性的比较,常常会用到的方法有: == .equals().但是两者使用起来有什么区别呢? 一.== java中的==是比较两个对象在JVM中的地址.比较好理 ...

  6. java为什么要重写hashCode和equals方法?

    如果不被重写(原生)的hashCode和equals是什么样的? 不被重写(原生)的hashCode值是根据内存地址换算出来的一个值. 不被重写(原生)的equals方法是严格判断一个对象是否相等的方 ...

  7. (转)Java 中正确使用 hashCode 和 equals 方法

    背景:最近在编写持久化对象时候遇到重写equals和hashCode方法的情况,对这两个方法的重写做一个总结. 链接:https://www.oschina.net/question/82993_75 ...

  8. 重写Java Object对象的hashCode和equals方法实现集合元素按内容判重

    Java API提供的集合框架中Set接口下的集合对象默认是不能存储重复对象的,这里的重复判定是按照对象实例句柄的地址来判定的,地址相同则判定为重复,地址不同不管内容如何都判定为不重复,这有时与需求不 ...

  9. Java入门系列之集合Hashtable源码分析(十一)

    前言 上一节我们实现了散列算法并对冲突解决我们使用了开放地址法和链地址法两种方式,本节我们来详细分析源码,看看源码中对于冲突是使用的哪一种方式以及对比我们所实现的,有哪些可以进行改造的地方. Hash ...

随机推荐

  1. Swifter.Json 可能是 .Net 平台迄今为止性能最佳的 Json 序列化库【开源】

    Json 简介 Json (JavaScript Object Notation) 是一种轻量级的数据交换格式.它作为目前最欢迎的数据交换格式,也是各大开源贡献者的必争之地,如:阿里爸爸的 fastj ...

  2. vue实现手机号码的校验(防抖函数的应用场景)

    上一篇博文我们讲到了节流函数的应用场景,我们知道了节流函数可以用在模糊查询.scroller.onresize等场景:今天这篇我们来讲防抖函数的应用场景:: 通过上一篇博文的学习,我们知道了防抖函数的 ...

  3. 基于Docker搭建Jumpserver堡垒机操作实践

    一.背景 笔者最近想起此前公司使用过的堡垒机系统,觉得用的很方便,而现在的公司并没有搭建此类系统,想着以后说不定可以用上:而且最近也有点时间,因此来了搭建堡垒机系统的兴趣,在搭建过程中参考了比较多的文 ...

  4. java设计模式8.迭代子模式、责任链模式、命令模式

    迭代子模式 迭代子模式可以顺序地访问一个聚集中的元素而不必暴露聚集的内部表象.它将迭代逻辑封装到一个独立的迭代子对象中,从而与聚集本身分开.迭代子对象是对遍历的抽象化,不同的聚集对象可以提供相同的迭代 ...

  5. 那些让你觉得自己是个傻B的题目集锦(大神的降维打击合集)

    一起过来排好队,进来挨打 1.Leetcode tag-LinkList 109.convert sorted list to binary search tree 2Leetcode tag-Arr ...

  6. Python数据类型详解——字典

    Python数据类型详解--字典 引子 已经学习了列表,现在有个需求--把公司每个员工的姓名.年龄.职务.工资存到列表里,你怎么存? staff_list = [ ["Kwan", ...

  7. HDU-6229 ICPC-沈阳M- Wandering Robots 概率

    HDU - 6229 题意: 在一个n*n的地图中,有一个初始在(0,0)位子的机器人,每次等概率的向相邻的格子移动或者留在原地.问最后留在格子(x,y)(x+y>=n-1)的地方的概率. 思路 ...

  8. 背包形动态规划 fjutoj2375 金明的预算方案

    金明的预算方案 TimeLimit:1000MS  MemoryLimit:128MB 64-bit integer IO format:%lld   Problem Description 金明今天 ...

  9. Codeforces Round #479 (Div. 3) B. Two-gram

    原题代码:http://codeforces.com/contest/977/problem/B 题解:有n个字符组成的字符串,输出出现次数两个字符组合.例如第二组样例ZZ出现了两次. 方法:比较无脑 ...

  10. Vue之render渲染函数和JSX的应用

    一.模板缺陷 模板的最大特点是扩展难度大,不易扩展.可能会造成逻辑冗余 <Level :type="1">哈哈</Level> <Level :typ ...