equals() 和 hashCode()

作为 Java 开发者,我们经常会用到 equals()hashCode() 这两个方法。 它们是 Object 类中定义的基础方法,看似简单,但如果理解不透彻,很容易在实际开发中踩坑。 本文将深入探讨这两个方法的作用、区别、以及如何正确地重写它们。

1. equals() 方法:判断对象是否相等

equals() 方法用于比较两个对象是否"相等"。 默认情况下,Object 类的 equals() 方法比较的是两个对象的 引用 是否相等,也就是说,只有当两个对象指向内存中的同一个地址时,equals() 方法才会返回 true

复制代码
Object obj1 = new Object();
Object obj2 = obj1; // obj2 指向与 obj1 相同的对象

System.out.println(obj1.equals(obj2)); // 输出 true (引用相等)

Object obj3 = new Object(); // obj3 指向一个新的对象

System.out.println(obj1.equals(obj3)); // 输出 false (引用不相等)

然而,在很多情况下,我们希望根据对象的 内容 来判断是否相等,而不是根据引用。 例如,对于 String 类,我们希望只要两个字符串的内容相同,就认为它们相等。 这时,我们就需要 重写 equals() 方法。

复制代码
String str1 = new String("hello");
String str2 = new String("hello");

System.out.println(str1.equals(str2)); // 输出 true (String 类重写了 equals() 方法)

重写 equals() 方法的原则:

  • 自反性: 对于任何非空对象 xx.equals(x) 必须返回 true
  • 对称性: 对于任何非空对象 xy,如果 x.equals(y) 返回 true,那么 y.equals(x) 必须返回 true
  • 传递性: 对于任何非空对象 xyz,如果 x.equals(y) 返回 true,并且 y.equals(z) 返回 true,那么 x.equals(z) 必须返回 true
  • 一致性: 对于任何非空对象 xy,如果 xy 的比较操作所用到的信息没有被修改,那么多次调用 x.equals(y) 要么始终返回 true,要么始终返回 false
  • 非空性: 对于任何非空对象 xx.equals(null) 必须返回 false

一个重写 equals() 方法的例子:

复制代码
import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

2. hashCode() 方法:生成对象的哈希码

hashCode() 方法用于返回对象的哈希码值。 哈希码是一个整数,主要用于在哈希表(例如 HashMapHashSet)中快速查找对象。

默认情况下,Object 类的 hashCode() 方法通常(但不保证)返回基于对象 内存地址 的一个整数值。

hashCode() 方法的通用约定:

  • 一致性: 在应用程序执行期间,只要对象的 equals 方法的比较操作所用到的信息没有被修改,那么对这同一个对象调用多次,hashCode 方法都必须始终如一地返回同一个整数。
  • 相等性: 如果两个对象根据 equals(Object) 方法是相等的,那么调用这两个对象中任意一个对象的 hashCode 方法,都必须产生同一个整数结果。
  • 可选性: 如果两个对象根据 equals(Object) 方法是不相等的,那么调用这两个对象中任意一个对象的 hashCode 方法,不 要求 产生不同的整数结果。 但是,程序员应该意识到,给不相等的对象产生截然不同的整数结果,有可能提高散列表(hash table)的性能。

重点:如果两个对象 equals() 相等,那么它们的 hashCode() 必须相等!

3. 为什么需要同时重写 equals()hashCode()

这是本文最重要的部分。 当你重写 equals() 方法时,必须同时重写 hashCode() 方法,以保证 hashCode() 方法的通用约定得到满足。

不重写 hashCode() 会导致什么问题?

如果你只重写了 equals() 方法,而没有重写 hashCode() 方法,那么相等的对象(根据 equals() 方法判断)会因为 Object 类的默认 hashCode() 实现而产生不同的哈希码。 这会导致在使用哈希表(例如 HashMapHashSet)时出现问题,例如查找失败或存储重复的对象。

举例说明:

复制代码
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }

    // 缺少 hashCode() 方法!
}

public class Main {
    public static void main(String[] args) {
        Map<Person, String> map = new HashMap<>();

        Person person1 = new Person("Alice", 30);
        Person person2 = new Person("Alice", 30);  // 与 person1 相等

        map.put(person1, "Alice's information");

        // 尝试获取与 person1 相等的 person2 的信息
        String information = map.get(person2);

        System.out.println(information);  // 输出 null!
    }
}

展开

在这个例子中,person1person2 根据 equals() 方法是相等的。 但是,由于没有重写 hashCode() 方法,它们具有不同的哈希码,因此被 HashMap 存储在不同的哈希桶中。 当你尝试使用 person2HashMap 中获取信息时,HashMap 会根据 person2 的哈希码找到一个错误的哈希桶,导致查找失败,返回 null

正确的做法:

复制代码
import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }

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

现在,person1person2 具有相同的哈希码,HashMap 可以正确地存储和查找它们。

相关推荐
咩咩啃树皮31 分钟前
第40篇:Vue3组件化开发精讲——组件拆分、复用、父子通信、工程化架构
java·前端·架构
灯澜忆梦1 小时前
GO_并发编程---定时器
开发语言·后端·golang
鱟鲥鳚1 小时前
Spring Boot 集成 LangChain4j:从模型调用到 Tool Calling(Demo版)
java·spring boot
-银雾鸢尾-1 小时前
C#中的StringBuilder相关方法
开发语言·c#
-银雾鸢尾-1 小时前
C#中结构体与类的区别;抽象类与接口的区别
开发语言·c#
大模型码小白2 小时前
【Python零基础教程】继承、多态与魔法函数:面向对象编程三大核心特性详解
java·大数据·开发语言·人工智能·python·ai编程
腾渊信息科技公司3 小时前
Spring Boot对接MES实战:视觉检测数据自动同步方案
java·人工智能·spring boot·后端·计算机视觉·ai·软件需求
爱笑的源码基地4 小时前
高并发 Redis 缓存门诊HIS系统源码,含财务统计药房进销存
java·程序·门诊系统·诊所系统·云诊所源码
wuqingshun3141594 小时前
TCP超时重传机制是为了解决什么问题?
java
段一凡-华北理工大学4 小时前
向量数据库实战:选型、调优与落地~系列文章12:文本分块策略实战:chunk_size 怎么选?重叠多少?
开发语言·数据库·后端·oracle·rust·工业智能体·高炉智能化