• 两个对象相等(==、equals、hashCode)详解


    1. == 和 equals

    在 Java 中,判断两个对象是否相等,使用的是 ==equals

    ==equals 的区别:

    • == 判断两对象相等,是判断它们的地址是否相等

    • equals 未被重写,也是通过判断地址来判断相等,等同于 ==

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

      但,当我们的业务有特殊需求的时候,往往会 重写 equals,如:String 中的 equals 就被重写

          // 地址相等 => 相等
          // 字符串内容相同 => 相等
          public boolean equals(Object anObject) {
              if (this == anObject) {
                  return true;
              }
              if (anObject instanceof String) {
                  String anotherString = (String)anObject;
                  // String底层是用 char value[]存储字符的
                  int n = value.length;
                  if (n == anotherString.value.length) {
                      char v1[] = value;
                      char v2[] = anotherString.value;
                      int i = 0;
                      while (n-- != 0) {
                          if (v1[i] != v2[i])
                              return false;
                          i++;
                      }
                      return true;
                  }
              }
              return false;
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    2. hashCode

    1. hash 概述

    hash: 散列算法,用来标志 文件/数据 的唯一性,相当于一种指纹。

    • 特点:
      • 正向快速:明文 + hash算法 => 快速获取 hash值
      • 逆向困难:几乎不可能根据给定 hash值 推导出密文
      • 输入敏感:原始输入信息轻微不同就会导致 hash 值 有很大的区别
      • 避免冲突:两段不同的明文几乎不可能出现相同的hash值

    hash表: 存储 hash值 。

    2. hashCode

    1. 概念

    hashCode 并不是对象的地址!!!

    • 每个对象都有一个 hashCode,其代表的是,对象存入散列表(HashTableHashMapHashSet)的地址
    • 只有要将对象存入 散列表 的时候才会用到 hashCode

    获取 hashCode :

            Object obj = new Object();
            System.out.println(obj);
            int hashCode = obj.hashCode();
            System.out.println("hashCode: " + hashCode);
            System.out.println("hashCode16进制: " + Integer.toHexString(hashCode));
            System.out.println("使用最初的hashCode算法:" + System.identityHashCode(hashCode));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    java.lang.Object@1b6d3586
    hashCode: 460141958
    hashCode16进制: 1b6d3586
    使用最初的hashCode算法:1163157884
    
    • 1
    • 2
    • 3
    • 4

    通过 JVM Options 指定 hashCode 的生成策略: N的取值在 [0,4]之间的随机整数,当N不在此区间,就会使用默认的生成策略。

    -XX:hashCode=N
    
    • 1

    2. 获取对象地址

    既然已经知道 hashCode 与对象的存储地址没关系,那么我们想要获取对象地址咋办?

    • 引入依赖:

      <dependency>
          <groupId>org.openjdk.jolgroupId>
          <artifactId>jol-coreartifactId>
          <version>0.16version>
      dependency>
      
      • 1
      • 2
      • 3
      • 4
      • 5
    • VM.current().addressOf(obj) 获取对象地址

              Object obj = new Object();
              String str = new String("123");
              long strAddress = VM.current().addressOf(str);
              long objAddress = VM.current().addressOf(obj);
      
      • 1
      • 2
      • 3
      • 4

    3. hashCode 与 equals

    1. 两者关系

    重写了 equals,不一定要重写 hashCode !!!

    只有要将对象存入 散列表 ,并且已经重写了 equals,才需要重写 hashCode !!!

    在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同


    先来简单回顾一下 HashMap 存储 key-value 的过程:

    • 计算 hash 值:key.hashCode()^( key.hashCode() >>>16 ) 计算出hash值
    • 如果 没发生hash碰撞(两个对象的hash值相同),就把对象直接存入桶中
      如果 发生了hash碰撞,就判断 key 是否相等,并使用 链表/红黑树 进行数据处理

    HashMap 中判断两个对象相等的条件时:key相等 且 value相等

            public final boolean equals(Object o) {
                if (o == this)
                    return true;
                if (o instanceof Map.Entry) {
                    Map.Entry<?,?> e = (Map.Entry<?,?>)o;
                    if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                        return true;
                }
                return false;
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    所以,在计算对象的hash值,并将其装入散列表的时候,需要计算其 key的 hashCode ,也需要计算其 value的 hashCode

            public final int hashCode() {
                return Objects.hashCode(key) ^ Objects.hashCode(value);
            }
    
    • 1
    • 2
    • 3

    通过参考 HashMap 重写 HashMapHashMap 的思路,我们就能自己重写这两个方法。

    2. 重写 equals并 重写 hashCode

    1. 只重写 equals

    我们自定义对象 User,重写了其 equals (使得 name 相等,两个对象就相等)。

    代码:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private String name;
        private Integer age;
    
        /**
         * @Description 名字相同也返回true
         * @param obj
         * @return boolean
        */
        @Override
        public boolean equals(Object obj) {
            if (obj == null || !(obj instanceof User)) return false;
            if (obj == this) return true;
            User user = (User) obj;
            return (user.name .equals(this.name));
        }
    
        public static void main(String[] args) {
            Set<Object> set = new HashSet<>();
            User user1 = new User("张三", 3);
            User user2 = new User("张三", 4);
            User user3 = new User("李四", 5);
            set.add(user1);
            set.add(user2);
            set.add(user3);
    
            System.out.println(user1.equals(user2));
            System.out.println(set);
            set.forEach( item -> {
                System.out.print(item.hashCode() + " ");
            });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    输出:

    分析:

    • HashSet 不能装重复对象,而 user1user2 是同一对象(equals 返回 true),但是两者都被装入了 HashSet,这逻辑明显就是错误的
    • 这就是 equals 返回 true 的范围 > hashCode 相等的范围 导致的

    2. 重写 equals 并重写 hashCode

    在上述案例的基础上,重写一下 hashCode

    代码:

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

    输出:

    分析:

    • user1user2 是同一对象(equals 返回 true),所以,只装入了 user1
    • 这就是重写了两方法之后, equals 返回 true 的范围 = hashCode 相等的范围的结果

    那么,我判断对象相等,需要其 nameage 都相等呢?

    先重写 equals

        @Override
        public boolean equals(Object obj) {
            if (obj == null || !(obj instanceof User)) return false;
            if (obj == this) return true;
            User user = (User) obj;
            return (user.name .equals(this.name) && user.age == this.age);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    hashCode 应该这样写:

        @Override
        public int hashCode() {
            return name.hashCode() ^ age.hashCode();
        }
    
    • 1
    • 2
    • 3
    • 4

    main 方法:

        public static void main(String[] args) {
            Set<Object> set = new HashSet<>();
            User user1 = new User("张三", 3);
            User user2 = new User("张三", 1);
            User user3 = new User("李四", 5);
            User user4 = new User("李四", 5);
            set.add(user1);
            set.add(user2);
            set.add(user3);
            set.add(user4);
    
            System.out.println(user1.equals(user2));
            System.out.println(user3.equals(user4));
            System.out.println(set);
            set.forEach( item -> {
                System.out.print(item.hashCode() + " ");
            });
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    输出:

    3. 小结

    • 重写了 equals 不一定要重写 hashCode
      • 在没有使用 散列表(HashTableHashMapHashSet)的时候,hashCode 就是一串没有人调用的整数
    • 在 散列表 中,两个对象相等(equals 返回 true),那么他们的 hashCode 一定相同
      • 一旦使用了散列表,并且重写了 equals,那么就必须重写 hashCode
  • 相关阅读:
    数字验证学习笔记——SystemVerilog芯片验证12 ——句柄的使用&包的使用
    javaweb JAVA JSP聊天室程序源码(局域网聊天系统 即时通讯)网页聊天系统
    GO面试一定要看看这些面试题
    linux精通 4.1
    四川九联代工M301H hi3798 mv300 mt7668魔百和 强刷和TTL线刷(救砖)经验分享
    C语言中文网 - Shell脚本 - 8
    java计算机毕业设计高校学生资助管理信息系统MyBatis+系统+LW文档+源码+调试部署
    HCIP-AI语音处理理论、应用
    不同规模的企业如何借助宁盾LDAP统一用户认证实现安全和效率需求?
    【软考复习系列】计算机网络易错知识点记录
  • 原文地址:https://blog.csdn.net/m0_54355172/article/details/128114400