浅谈 equals() 和 hashCode()

Object 类中定义了 equals()hashCode()

在 Java 开发中大量涉及这两个方法,特别是集合框架。

方法作用

  1. hashCode():用于计算对象哈希值。
  2. equals():用于比较对象是否相等。

1、方法实现

1.1、默认实现

基于对象引用地址

  1. equals():比较两个对象引用是否指向同一内存地址。

  2. hashCode():返回对象内存地址(本地方法)。

    public boolean equals(Object obj) {
        return (this == obj);
    }
    
    public native int hashCode();
    

1.2、方法重写

为什么要重写

  • Object 默认实现:只有同一个对象引用equals()hashCode() 才会相等。
  • 在实际业务中,需要为不同对象成对重写方法,才能自定义比较规则

不重写的后果?

以 HashMap 为例:由于对象引用不同,即使属性完全相同也无法获取到集合中的元素。

HashMap<User> userMap = new HashMap()<>;

public void method1() {
    User u1 = new User("secret", 20);
    userMap.put(u1, "user1");
}

public void method2() {
    User u2 = new User("secret", 20);
    userMap.get(u2);	// 能获取到吗?
}

2、equals()

2.1、运算符 ==

  1. 基本类型:比较
  2. 引用类型:比较内存地址equals() 默认实现)。

2.2、重写 equals()

通常认为两个对象的属性值完全相同,则对象相等

根据这个规则来重写 equals() 即可。

示例:按以下步骤重写。

  1. 参数检验

    • 相同引用
    • 判空
    • 判断类型:根据对类型要求的严格性,选择合适的方式。
      • getClass()(严格):返回运行时具体类型,不考虑继承关系。
      • instanceOf(宽松):测试对象是否为类的实例(或接口实现类)。
  2. 比较属性:先向下转型,再比较属性值。

    1. 基本类型:使用 ==

    2. 引用类型:使用 Objects.equals() 方法,避免 NPE

      @Override
      public boolean equals(Object obj) {
          if (this == obj) {
              return true;
          }
      
          // 若严格要求类型一致,条件二改成 this.getClass() != obj.getClass()
          if (obj == null || !obj instanceof Person) {
              return false;
          }
          
          Person p = (Person) obj;
          
          // 不使用 xxx.equals(xxx),避免NPE
          return Objects.equals(this.name, p.name)
              && this.age == p.age;
      
      }
      

Objects 工具类

传入两个待比较的参数,可有效避免 NPE

public static boolean equals(Object a, Object b) {
    return (a == b) || (a != null && a.equals(b));
}

3、hashCode()

3.1、含义

先了解👉 哈希及 Java 实现

  • hashCode() 本质:一种哈希算法,用于哈希表(如 HashMap)。
  • 意义
    1. 假如没有 hashCode(),只使用 equals() 会导致频繁的重复比较。
    2. hashCode() 提高容器存储和查找效率。

3.2、要求

Java 对 hashCode() 的要求

  1. 在 Java 程序的一次运行期间,多次调用同一个对象的 hashCode(),必须返回一致的整数

    • 前提是该对象 equals() 比较中使用的信息没有被修改。
    • 在一次运行期间必须保持一致,不同的运行期间可以不一致。
  2. 针对两个对象,equals() 相等,则 hashCode() 相等

    描述 成立? 说明
    逆命题 hashCode() 相等,则 equals() 相等 存在哈希冲突
    否命题 equals() 不相等,则 hashCode() 不相等 存在哈希冲突
    逆否命题 hashCode 不相等,则 equals() 不相等

3.3、重写 hashCode()

没有完美的哈希算法,hashCode() 应满足以下原则。

  1. 尽量减少哈希冲突
  2. 哈希函数计算的结果,尽量均匀分布在哈希表的桶(bucket)中。

示例:使用 Objects 工具类的方法。

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

哈希算法

Objects.hash(...)

  1. 方法参数:可变参数(数组)

  2. 方法内容:调用 Arrays.hashCode(...)

    public static int hash(Object... values) {
        return Arrays.hashCode(values);
    }
    

Arrays.hashCode(...)

  1. 方法参数:数组

  2. 方法内容:计算哈希值。

    1. 遍历数组:基于多个元素计算。

    2. 迭代计算:基于迭代的方式计算( hash = 31 * hash + hash(i))。

      public static int hashCode(Object a[]) {
          if (a == null)
              return 0;
      
          int result = 1;
      
          for (Object element : a)
              result = 31 * result + (element == null ? 0 : element.hashCode());
      
          return result;
      }
      

优选乘子 31

选用 31 作为乘子的好处

  1. 奇质数:计算时可减少哈希冲突。
  2. 性能优化
    1. 代码频繁执行时,JVM 优化 31 * i 为位运算 (i<<5) - 1
    2. 位运算的执行效率高。
posted @ 2023-03-18 18:37  Jaywee  阅读(26)  评论(0编辑  收藏  举报

👇