作为 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() 方法的原则:
- 自反性: 对于任何非空对象
x,x.equals(x)必须返回true。 - 对称性: 对于任何非空对象
x和y,如果x.equals(y)返回true,那么y.equals(x)必须返回true。 - 传递性: 对于任何非空对象
x、y和z,如果x.equals(y)返回true,并且y.equals(z)返回true,那么x.equals(z)必须返回true。 - 一致性: 对于任何非空对象
x和y,如果x和y的比较操作所用到的信息没有被修改,那么多次调用x.equals(y)要么始终返回true,要么始终返回false。 - 非空性: 对于任何非空对象
x,x.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() 方法用于返回对象的哈希码值。 哈希码是一个整数,主要用于在哈希表(例如 HashMap、HashSet)中快速查找对象。
默认情况下,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() 实现而产生不同的哈希码。 这会导致在使用哈希表(例如 HashMap、HashSet)时出现问题,例如查找失败或存储重复的对象。
举例说明:
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!
}
}
展开
在这个例子中,person1 和 person2 根据 equals() 方法是相等的。 但是,由于没有重写 hashCode() 方法,它们具有不同的哈希码,因此被 HashMap 存储在不同的哈希桶中。 当你尝试使用 person2 从 HashMap 中获取信息时,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);
}
}
现在,person1 和 person2 具有相同的哈希码,HashMap 可以正确地存储和查找它们。