前言:Java 对象如果要比较是否相等,则需要重写 equals 方法,同时重写 hashCode 方法,而且 hashCode 方法里面使用质数 31。接下来看看各种为什么。

一、需求:

  对比两个对象是否相等。对于下面的 User 对象,只需姓名和年龄相等则认为是同一个对象。

二、解决方案:

  需要重写对象的 equals 方法和 hashCode 方法

  1. package com.yule.user.entity;
  2.  
  3. import org.springframework.util.StringUtils;
  4.  
  5. /**
  6. * 用户实体
  7. *
  8. * @author yule
  9. * @date 2018/8/6 21:51
  10. */
  11. public class User {
  12. private String id;
  13. private String name;
  14. private String age;
  15.  
  16. public User(){
  17.  
  18. }
  19.  
  20. public User(String id, String name, String age){
  21. this.id = id;
  22. this.name = name;
  23. this.age = age;
  24. }
  25.  
  26. public String getId() {
  27. return id;
  28. }
  29.  
  30. public void setId(String id) {
  31. this.id = id;
  32. }
  33.  
  34. public String getName() {
  35. return name;
  36. }
  37.  
  38. public void setName(String name) {
  39. this.name = name;
  40. }
  41.  
  42. public String getAge() {
  43. return age;
  44. }
  45.  
  46. public void setAge(String age) {
  47. this.age = age;
  48. }
  49.  
  50. @Override
  51. public String toString() {
  52. return this.id + " " + this.name + " " + this.age;
  53. }
  54.  
  55. @Override
  56. public boolean equals(Object obj) {
  57. if(this == obj){
  58. return true;//地址相等
  59. }
  60.  
  61. if(obj == null){
  62. return false;//非空性:对于任意非空引用x,x.equals(null)应该返回false。
  63. }
  64.  
  65. if(obj instanceof User){
  66. User other = (User) obj;
  67. //需要比较的字段相等,则这两个对象相等
  68. if(equalsStr(this.name, other.name)
  69. && equalsStr(this.age, other.age)){
  70. return true;
  71. }
  72. }
  73.  
  74. return false;
  75. }
  76.  
  77. private boolean equalsStr(String str1, String str2){
  78. if(StringUtils.isEmpty(str1) && StringUtils.isEmpty(str2)){
  79. return true;
  80. }
  81. if(!StringUtils.isEmpty(str1) && str1.equals(str2)){
  82. return true;
  83. }
  84. return false;
  85. }
  86.  
  87. @Override
  88. public int hashCode() {
  89. int result = 17;
  90. result = 31 * result + (name == null ? 0 : name.hashCode());
  91. result = 31 * result + (age == null ? 0 : age.hashCode());
  92. return result;
  93. }
  94. }

三、测试

  1、创建两个对象,名字和年龄相等则对象 equals 为 true。

  1. @Test
  2. public void testEqualsObj(){
  3. User user1 = new User("1", "xiaohua", "14");
  4. User user2 = new User("2", "xiaohua", "14");
  5. System.out.println((user1.equals(user2)));//打印为 true
  6. }

四、为什么要重写 equals 方法

  因为不重写 equals 方法,执行 user1.equals(user2) 比较的就是两个对象的地址(即 user1 == user2),肯定是不相等的,见 Object 源码:

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }

五、为什么要重写 hashCode 方法

  既然比较两个对象是否相等,使用的是 equals 方法,那么只要重写了 equals 方法就好了,干嘛又要重写 hashCode 方法呢?

  其实当 equals 方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。那这又是为什么呢?看看下面这个例子就懂了。

  User 对象的 hashCode 方法如下,没有重写父类的 hashCode 方法

  1. @Override
  2. public int hashCode() {
  3. return super.hashCode();
  4. }

  使用 hashSet

  1. @Test
  2. public void testHashCodeObj(){
  3. User user1 = new User("1", "xiaohua", "14");
  4. User user2 = new User("2", "xiaohua", "14");
  5. Set<User> userSet = new HashSet<>();
  6. userSet.add(user1);
  7. userSet.add(user2);
  8. System.out.println(user1.equals(user2));
  9. System.out.println(user1.hashCode() == user2.hashCode());
  10. System.out.println(userSet);
  11. }

  结果

  显然,这不是我们要的结果,我们是希望两个对象如果相等,那么在使用 hashSet 存储时也能认为这两个对象相等。

  通过看 hashSet 的 add 方法能够得知 add 方法里面使用了对象的 hashCode 方法来判断,所以我们需要重写 hashCode 方法来达到我们想要的效果。

  将 hashCode 方法重写后,执行上面结果为

  1. @Override
  2. public int hashCode() {
  3. int result = 17;
  4. result = 31 * result + (name == null ? 0 : name.hashCode());
  5. result = 31 * result + (age == null ? 0 : age.hashCode());
  6. return result;
  7. }

  所以:hashCode 是用于散列数据的快速存取,如利用 HashSet/HashMap/Hashtable 类来存储数据时,都会根据存储对象的 hashCode 值来进行判断是否相同的。

六、如何重写 hashCode

  生成一个 int 类型的变量 result,并且初始化一个值,比如17

  对类中每一个重要字段,也就是影响对象的值的字段,也就是 equals 方法里有比较的字段,进行以下操作:a. 计算这个字段的值 filedHashValue = filed.hashCode(); b. 执行 result = 31 * result + filedHashValue;

七、为什么要使用 31

  看一看 String hashCode 方法的源码:

  1. /**
  2. * Returns a hash code for this string. The hash code for a
  3. * {@code String} object is computed as
  4. * <blockquote><pre>
  5. * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
  6. * </pre></blockquote>
  7. * using {@code int} arithmetic, where {@code s[i]} is the
  8. * <i>i</i>th character of the string, {@code n} is the length of
  9. * the string, and {@code ^} indicates exponentiation.
  10. * (The hash value of the empty string is zero.)
  11. *
  12. * @return a hash code value for this object.
  13. */
  14. public int hashCode() {
  15. int h = hash;
  16. if (h == 0 && value.length > 0) {
  17. char val[] = value;
  18.  
  19. for (int i = 0; i < value.length; i++) {
  20. h = 31 * h + val[i];
  21. }
  22. hash = h;
  23. }
  24. return h;
  25. }

  可以从注释看出:空字符串的 hashCode 方法返回是 0。并且注释中也给了个公式,可以了解了解。

  String 源码中也使用的 31,然后网上说有这两点原因:

原因一:更少的乘积结果冲突

  31是质子数中一个“不大不小”的存在,如果你使用的是一个如2的较小质数,那么得出的乘积会在一个很小的范围,很容易造成哈希值的冲突。而如果选择一个100以上的质数,得出的哈希值会超出int的最大范围,这两种都不合适。而如果对超过 50,000 个英文单词(由两个不同版本的 Unix 字典合并而成)进行 hash code 运算,并使用常数 31, 33, 37, 39 和 41 作为乘子,每个常数算出的哈希值冲突数都小于7个(国外大神做的测试),那么这几个数就被作为生成hashCode值得备选乘数了。

  所以从 31,33,37,39 等中间选择了 31 的原因看原因二。

原因二:31 可以被 JVM 优化

  JVM里最有效的计算方式就是进行位运算了:

  * 左移 << : 左边的最高位丢弃,右边补全0(把 << 左边的数据*2的移动次幂)。
  * 右移 >> : 把>>左边的数据/2的移动次幂。
  * 无符号右移 >>> : 无论最高位是0还是1,左边补齐0。   

所以 : 31 * i = (i << 5) - i(左边  31*2=62,右边   2*2^5-2=62) - 两边相等,JVM就可以高效的进行计算啦。。。

Java 如何重写对象的 equals 方法和 hashCode 方法的更多相关文章

  1. Java基础系列-equals方法和hashCode方法

    原创文章,转载请标注出处:<Java基础系列-equals方法和hashCode方法> 概述         equals方法和hashCode方法都是有Object类定义的. publi ...

  2. HashSet中存方用户自己定义数据类型数据,重写equals方法和hashCode方法

    import java.util.Set; import java.util.HashSet; public class SetTest { public static void main(Strin ...

  3. 详解equals()方法和hashCode()方法

    前言 Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,hashCode()方法用于计算对象的哈希码.equals()和hashCode()都不是final方 ...

  4. 关于Object类的equals方法和hashCode方法

    关于Object类的equals的特点,对于非空引用: 1.自反性:x.equals(x) return true : 2.对称性:x.equals(y)为true,那么y.equals(x)也为tr ...

  5. 详解 equals() 方法和 hashCode() 方法

    创建实体类时,最好重写超类(Object)的hashCode()和equals()方法 equals()方法: 通过该实现可以看出,Object类的实现采用了区分度最高的算法,即只要两个对象不是同一个 ...

  6. equals()方法和hashCode()方法详解

    equals()方法和hashCode()方法详解 1. Object类中equals()方法源代码如下所示: /** * Object类中的equals()方法 */ public boolean ...

  7. JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法

    在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address { priva ...

  8. Java equals()方法和hashCode()方法

    equals()方法 如果满足了以下任何一个条件,就不需要覆盖equals()方法: 1 类的每个实例本质上都是唯一的. 2 不关心类是否提供了“逻辑相等”的测试功能. 3 父类已经覆盖了equals ...

  9. java中equals方法和hashcode方法的区别和联系,以及为什么要重写这两个方法,不重写会怎样

    一.在Object类中的定义为:public native int hashCode();是一个本地方法,返回的对象的地址值.但是,同样的思路,在String等封装类中对此方法进行了重写.方法调用得到 ...

随机推荐

  1. Debug Dart at External Terminal

    launch.json { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions ...

  2. A - Subsequence (算法 二分 )

    点击打开链接 A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 1 ...

  3. flask之flask_sqlalchemy

    一. 介绍 SQLAlchemy是一个基于Python实现的ORM框架.该框架建立在 DB API之上,使用关系对象映射进行数据库操作,简言之便是:将类和对象转换成SQL,然后使用数据API执行SQL ...

  4. Educational Codeforces Round 34 (Rated for Div. 2) D - Almost Difference(高精度)

    D. Almost Difference Let's denote a function You are given an array a consisting of n integers. You ...

  5. SELECT 三级联动 [转]

    <!DOCTYPE html> <html> <head> <meta charset=gbk /> <title>selectList&l ...

  6. day 43 mysql 学习 以及pymysql 学习

    前情提要: 本次主要学习sql 的难点, 多表查询以及连接python  一:多表关联 >多表查询的缺点 二:单表的连表查询[自关联查询] 三:子查询 >主查询 >主查询 >主 ...

  7. nacicat premium 快捷键

    1.ctrl+q          打开查询窗口 2.ctrl+/           注释sql语句 3.ctrl+shift +/  解除注释 4.ctrl+r          运行查询窗口的s ...

  8. 【GDOI2018模拟8】 数学竞赛 三角函数性质+记忆化搜索

    数据范围:p,q≤20. 只能说我整个人傻逼了..... 我们考虑三角函数的部分性质: $sin(x)=\sqrt{ 1-cos^2(x)}$ $cos(x)=\sqrt{1-sin^2(x)}$ $ ...

  9. JS优先队列排序。出队时,先找出优先级最高的元素,再按照先进先出出队。

    JS优先队列排序.出队时,先找出优先级最高的元素,再按照先进先出出队. /* * 优先队列 * 出队时,先找出优先级最高的元素,再按照先进先出出队. * */ function Queue(){ th ...

  10. TapTap推广统计逻辑

    当我们在Taptap上访问某款游戏时,比如https://www.taptap.com/app/34762,taptap会记录下这次访问,它是怎么做的呢. 首先,用记事本打开这个网址,在head部分看 ...