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 可以正确地存储和查找它们。

相关推荐
DKPT22 分钟前
Java桥接模式实现方式与测试方法
java·笔记·学习·设计模式·桥接模式
Eiceblue1 小时前
【免费.NET方案】CSV到PDF与DataTable的快速转换
开发语言·pdf·c#·.net
好奇的菜鸟2 小时前
如何在IntelliJ IDEA中设置数据库连接全局共享
java·数据库·intellij-idea
m0_555762902 小时前
Matlab 频谱分析 (Spectral Analysis)
开发语言·matlab
DuelCode3 小时前
Windows VMWare Centos Docker部署Springboot 应用实现文件上传返回文件http链接
java·spring boot·mysql·nginx·docker·centos·mybatis
浪裡遊3 小时前
React Hooks全面解析:从基础到高级的实用指南
开发语言·前端·javascript·react.js·node.js·ecmascript·php
优创学社23 小时前
基于springboot的社区生鲜团购系统
java·spring boot·后端
幽络源小助理3 小时前
SpringBoot基于Mysql的商业辅助决策系统设计与实现
java·vue.js·spring boot·后端·mysql·spring
猴哥源码3 小时前
基于Java+springboot 的车险理赔信息管理系统
java·spring boot
lzb_kkk4 小时前
【C++】C++四种类型转换操作符详解
开发语言·c++·windows·1024程序员节