Java 里 hashCode() 和 equals() 的相关问题整理

本章的内容主要解决下面几个问题:

equals() 的作用是什么

equals() 与 == 的区别是什么

hashCode() 的作用是什么

hashCode() 和 equals() 之间有什么联系?


第1部分 equals() 的作用

equals() 的作用是 用来判断两个对象是否相等

equals() 定义在JDK的Object.java中。通过判断两个对象的地址是否相等(即,是否是同一个对象)来区分它们是否相等。源码如下:

既然Object.java中定义了equals()方法,这就意味着所有的Java类都实现了equals()方法,所有的类都可以通过equals()去比较两个对象是否相等。 但是,我们已经说过,使用默认的“equals()”方法,等价于“==”方法。因此,我们通常会重写equals()方法:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。

下面根据“类是否覆盖equals()方法”,将它分为2类。
(01) 若某个类没有覆盖equals()方法,当它的通过equals()比较两个对象时,实际上是比较两个对象是不是同一个对象。这时,等价于通过“==”去比较这两个对象。
(02) 我们可以覆盖类的equals()方法,来让equals()通过其它方式比较两个对象是否相等。通常的做法是:若两个对象的内容相等,则equals()方法返回true;否则,返回fasle。

下面,举例对上面的2种情况进行说明。

1.  “没有覆盖equals()方法”的情况

代码如下 (EqualsTest1.java)


运行结果

结果分析

我们通过 p1.equals(p2) 来“比较p1和p2是否相等时”。实际上,调用的Object.java的equals()方法,即调用的 (p1==p2) 。它是比较“p1和p2是否是同一个对象”。
而由 p1 和 p2 的定义可知,它们虽然内容相同;但它们是两个不同的对象!因此,返回结果是false。

 

2. “覆盖equals()方法的情况

我们修改上面的EqualsTest1.java覆盖equals()方法

代码如下 (EqualsTest2.java)


运行结果

结果分析

我们在EqualsTest2.java 中重写了Person的equals()函数:当两个Person对象的 name 和 age 都相等,则返回true。
因此,运行结果返回true。

 

讲到这里,顺便说一下java对equals()的要求。有以下几点:

 

现在,再回顾一下equals()的作用:判断两个对象是否相等。当我们重写equals()的时候,可千万不好将它的作用给改变了!

 


第2部分 equals() 与 == 的区别是什么?

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不试同一个对象。

equals() : 它的作用也是判断两个对象是否相等。但它一般有两种使用情况(前面第1部分已详细介绍过):
情况1,类没有覆盖equals()方法。则通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象。
情况2,类覆盖了equals()方法。一般,我们都覆盖equals()方法来两个对象的内容相等;若它们的内容相等,则返回true(即,认为这两个对象相等)。

 

下面,通过示例比较它们的区别。

代码如下

 

运行结果

结果分析

在EqualsTest3.java 中:
(01) p1.equals(p2)
这是判断p1和p2的内容是否相等。因为Person覆盖equals()方法,而这个equals()是用来判断p1和p2的内容是否相等,恰恰p1和p2的内容又相等;因此,返回true。

(02) p1==p2
这是判断p1和p2是否是同一个对象。由于它们是各自新建的两个Person对象;因此,返回false。

 


第3部分 hashCode() 的作用

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数。
虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
上面的散列表,指的是:Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet。

也就是说:hashCode() 在散列表中才有用,在其它情况下没用。在散列表中hashCode() 的作用是获取对象的散列码,进而确定该对象在散列表中的位置。

OK!至此,我们搞清楚了:hashCode()的作用是获取散列码。但是,散列码是用来干什么的呢?为什么散列表需要散列码呢?要解决这些问题,就需要理解散列表!关于散列表可以自行百度。

为了能理解后面的内容,这里简单的介绍一下散列码的作用。

下面,我们以HashSet为例,来深入说明hashCode()的作用。

假设,HashSet中已经有1000个元素。当插入第1001个元素时,需要怎么处理?因为HashSet是Set集合,它允许有重复元素。
“将第1001个元素逐个的和前面1000个元素进行比较”?显然,这个效率是相等低下的。散列表很好的解决了这个问题,它根据元素的散列码计算出元素在散列表中的位置,然后将元素插入该位置即可。对于相同的元素,自然是只保存了一个。
由此可知,若两个元素相等,它们的散列码一定相等;但反过来确不一定。在散列表中,
1、如果两个对象相等,那么它们的hashCode()值一定要相同;
2、如果两个对象hashCode()相等,它们并不一定相等。
注意:这是在散列表中的情况。在非散列表中一定如此!

 

对“hashCode()的作用”就谈这么多。

 


第4部分 hashCode() 和 equals() 的关系

接下面,我们讨论另外一个话题。网上很多文章将 hashCode() 和 equals 关联起来,有的讲的不透彻,有误导读者的嫌疑。在这里,我自己梳理了一下 “hashCode() 和 equals()的关系”。

我们以“类的用途”来将“hashCode() 和 equals()的关系”分2种情况来说明。

 

1. 第一种 不会创建“类对应的散列表”

这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!
这种情况下,equals() 用来比较该类的两个对象是否相等。而hashCode() 则根本没有任何作用,所以,不用理会hashCode()。

下面,我们通过示例查看类的两个对象相等 以及 不等时hashCode()的取值。

源码如下 (NormalHashCodeTest.java):


运行结果

从结果也可以看出:p1和p2相等的情况下,hashCode()也不一定相等。

 

2. 第二种 会创建“类对应的散列表”

这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突。

此外,在这种情况下。若要判断两个对象是否相等,除了要覆盖equals()之外,也要覆盖hashCode()函数。否则,equals()无效。
例如,创建Person类的HashSet集合,必须同时覆盖Person类的equals() 和 hashCode()方法。
如果单单只是覆盖equals()方法。我们会发现,equals()方法没有达到我们想要的效果。

参考代码 (ConflictHashCodeTest1.java):


运行结果

结果分析

我们重写了Person的equals()。但是,很奇怪的发现:HashSet中仍然有重复元素:p1 和 p2。为什么会出现这种情况呢?

这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。

 

下面,我们同时覆盖equals() 和 hashCode()方法。

参考代码 (ConflictHashCodeTest2.java):


运行结果

结果分析

这下,equals()生效了,HashSet中没有重复元素。
比较p1和p2,我们发现:它们的hashCode()相等,通过equals()比较它们也返回true。所以,p1和p2被视为相等。
比较p1和p4,我们发现:虽然它们的hashCode()相等;但是,通过equals()比较它们返回false。所以,p1和p4被视为不相等。

0

Java 随机数生成 Random 种子(seed)的作用

不同的构造函数

Random 通常用来作为随机数生成器,它有两个构造方法:

1.不含参构造方法:

2.含参构造方法:

都调用的 setSeed 方法:

可以看到,不含参构造方法每次都使用当前时间作为种子,而含参构造方法是以一个固定值作为种子。

什么是种子 seed 呢?

seed 是 Random 生成随机数时使用的参数:

Random 中最重要的就是 next(int) 方法,使用 seed 进行计算:

其他 nextXXX 方法都是调用的 next()。

比如 nextInt(int):

再比如 nextBoolean():

举个栗子:

分别用含参构造方法和不含参构造方法创建 5 个随机生成器对象,每个随机生成器再生产 8 个随机数,对比下结果:

 

再运行一次:


总结:

通过上述例子可以发现:

随机数是种子经过计算生成的。

不含参的构造函数每次都使用当前时间作为种子,随机性更强
而含参的构造函数其实是伪随机,更有可预见性

0

Java nanoTime 与 currentTimeMillis 比较

System.nanoTime与System.currentTimeMillis比较

currentTimeMillis返回的是系统当前时间和1970-01-01之前间隔时间的毫秒数,如果系统时间固定则方法返回值也是一定的(这么说是为了强调和nanoTime的区别),精确度是毫秒级别的。

nanoTime的返回值本身则没有什么意义,因为它基于的时间点是随机的,甚至可能是一个未来的时间,所以返回值可能为负数。但是其精确度为纳秒,相对高了不少。

currentTimeMillis不仅可以用来计算代码执行消耗的时间 ,也可以和Date类方便的转换。而nanoTime则不行。

可以这么说吧,currentTimeMillis是一个时钟,而nanoTime是一个计时器,你可以用时钟来计算时间差,也可以用来单纯的看时间,但是作为计时器的nanoTime则只能用来计算时间差,好在优点是精确度高。

currentTimeMillis是基于系统时间的,也就是说如果你在程序执行期间更改了系统时间则结果就会出错,而nanoTime是基于CPU的时间片来计算时间的,无法人为干扰
前面说了nanoTime基于的时间点是随机的,但是对于同一个JVM里,不同地方使用到的基点时间是一样的。

代码比较

 

总结如下:

1.currentTimeMillis精度上相对于nanoTime要输很多
2.nanoTime方法本身执行的时间相对于currentTimeMillis要多很多,所以应当只在精确度要求较高时使用nanoTime
3.currentTimeMillis是时钟,nanoTime是计时器

0

Java基础面试概括

Java基础面试概括

以下是提纲,链接在 https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/Java%E5%9F%BA%E7%A1%80%E7%9F%A5%E8%AF%86.md

  1. 面向对象和面向过程的区别
  2. Java 语言有哪些特点?
  3. 关于 JVM JDK 和 JRE 最详细通俗的解答 JVM
    JDK 和 JRE
  4. Oracle JDK 和 OpenJDK 的对比
  5. Java 和 C++的区别?
  6. 什么是 Java 程序的主类 应用程序和小程序的主类有何不同?
  7. Java 应用程序与小程序之间有哪些差别?
  8. 字符型常量和字符串常量的区别?
  9. 构造器 Constructor 是否可被 override?
  10. 重载和重写的区别 – 重载 – 重写
  11. Java 面向对象编程三大特性: 封装 继承 多态 封装
    继承
    多态
  12. String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
  13. 自动装箱与拆箱
  14. 在一个静态方法内调用一个非静态成员为什么是非法的?
  15. 在 Java 中定义一个不做事且没有参数的构造方法的作用
  16. import java 和 javax 有什么区别?
  17. 接口和抽象类的区别是什么?
  18. 成员变量与局部变量的区别有哪些?
  19. 创建一个对象用什么运算符?对象实体与对象引用有何不同?
  20. 什么是方法的返回值?返回值在类的方法里的作用是什么?
  21. 一个类的构造方法的作用是什么? 若一个类没有声明构造方法,该程序能正确执行吗? 为什么?
  22. 构造方法有哪些特性?
  23. 静态方法和实例方法有何不同
  24. 对象的相等与指向他们的引用相等,两者有什么不同?
  25. 在调用子类构造方法之前会先调用父类没有参数的构造方法,其目的是?
  26. == 与 equals(重要)
  27. hashCode 与 equals (重要) hashCode()介绍
    为什么要有 hashCode
    hashCode()与 equals()的相关规定
  28. 为什么 Java 中只有值传递?
  29. 简述线程、程序、进程的基本概念。以及他们之间关系是什么?
  30. 线程有哪些基本状态?
  31. 关于 final 关键字的一些总结
  32. Java 中的异常处理 Java 异常类层次结构图
    Throwable 类常用方法
    异常处理总结
  33. Java 序列化中如果有些字段不想进行序列化,怎么办?
  34. 获取用键盘输入常用的两种方法
  35. Java 中 IO 流 Java 中 IO 流分为几种?
    既然有了字节流,为什么还要有字符流?
    BIO,NIO,AIO 有什么区别?
  36. 常见关键字总结:static,final,this,super
  37. Collections 工具类和 Arrays 工具类常见方法总结
  38. 深拷贝 vs 浅拷贝
0