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