为什么要重写 Java 中的 equals 和 hashcode 方法?
来源:
Medium 本文重点介绍两个密切相关的方法:equals()和hashcode()。您将了解它们如何相互交互以及如何正确覆盖它们。
为什么我们要重写 equals() 方法?
在 Java 中,我们不能重载==、
+=、
-+等运算符的行为。他们按照给定的流程工作。例如,考虑
==运算符的操作。
== 运算符如何工作?
它检查比较的两个引用是否指向内存中的同一实例。仅当两个引用表示内存中的同一实例时,
==运算符才会计算为 true。我们看一下示例代码:
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
}
假设在您的程序中,您在不同的位置创建了两个Person 对象并想要比较它们。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println( person1 == person2 ); --> will print false!
从商业角度来看,两者看起来是一样的,对吧?但对于 JVM 来说它们并不相同。由于它们都是使用
new关键字创建的,因此这些实例位于不同的内存段中。因此
==运算符将返回
false。但是如果我们不能重写
==运算符,那么我们如何告诉 JVM 我们希望这两个对象得到相同的对待?
这就是.equals()方法发挥作用的地方。您可以重写
equals()来检查某些对象的某些字段是否具有相同的值,以便认为它们相等。您可以选择要比较的字段。如果我们说两个
Person对象只有具有相同的年龄和相同的名称才是相同的,那么 IDE 将生成类似这样的内容 来自动创建
equals() :
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
让我们回到之前的例子。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1 == person2 ); --> will print false!
System.out.println ( person1.equals(person2) ); --> will print true!
是的,我们不能重载
==运算符来按照我们想要的方式比较对象,但是 Java 为我们提供了另一种方式 - equals
()方法,我们可以根据需要重写该方法。
请记住,如果我们不在类中提供自定义版本的.equals()(也称为覆盖),则Object类中预定义的.equals()和==运算符的行为将相同。 默认的
equals()方法继承自
Object,将检查正在比较的两个实例在内存中是否相同!
为什么我们要重写 hashCode() 方法?
Java 中的某些数据结构(例如
HashSet和
HashMap)基于应用于这些元素的哈希函数来存储其元素。哈希函数是
hashCode()。如果我们可以选择重写
.equals()方法,那么我们也应该可以选择重写
hashCode()方法。这是有原因的。毕竟,继承自
Object的 hashCode() 的默认实现认为内存中的所有对象都是唯一的!但让我们回到这些哈希数据结构。这些数据结构有一个规则。
HashSet不能包含重复的值,HashMap不能包含重复的键。HashSet是使用
HashMap实现的,每个
HashSet值都作为键存储在
HashMap中。
HashMap是如何工作的?
HashMap是一个具有多个段的原生数组。每个段都有一个链表(
linkedList)。这个链表存储了我们的密钥。
HashMap使用hashCode()方法为每个键找到正确的 linkedList ,然后迭代该
linkedList的所有元素,并对每个元素应用
equals()方法以检查该元素是否包含在其中。
不允许有重复的密钥。当我们将某些内容放入
HashMap时,键将存储在这些链表之一中。该键将存储在哪个链表中由该键的
hashCode()方法的结果显示。也就是说,如果
key1.hashCode()结果为 4,则
key1将存储在现有
LinkedList 数组的第 4 段中。默认情况下,
hashCode()方法为每个实例返回不同的结果。如果我们有一个默认的
equals(),其行为类似于
==,将内存中的所有实例视为不同的对象,那么就不会有问题。
您可能还记得,在前面的示例中,我们说过,如果Person实例的年龄和名称相同,则 我们希望它们被视为相等。
Person person1 = new Person("Mike", 34);
Person person2 = new Person("Mike", 34);
System.out.println ( person1.equals(person2) ); --> will print true!
现在让我们创建一个映射来将这些实例存储为键,并以特定字符串作为值对。
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
在
Person类中,我们没有重写
hashCode方法,但是我们有一个重写的
equals方法。由于默认
hashCode对于person1.hashCode()和
person2.hashCode()的不同 Java 实例给出不同的结果,因此很有可能获得不同的结果。
我们的地图可以以不同的链接列表中的不同的人结束。
这违背了HashMap 的逻辑。
毕竟,HashMap不能有多个相同的键! 重点是继承自
Object类的默认hashCode()是不够的。即使我们重写了
Person类的
equals()方法。这就是为什么我们在重写
equals方法之后必须重写
hashCode()方法。现在让我们解决这个问题。我们需要重写
hashCode()方法,以便它考虑与
equals()相同的字段,即
age和
name。
public class Person {
private Integer age;
private String name;
..getters, setters, constructors
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return age == person.age &&
name.equals(person.name);
}
@Override
public int hashCode() {
int prime = 31;
return prime*Objects.hash(name, age);
}
在
hashCode()方法中,我们使用了一个简单的值(您可以使用任何其他值)。不过,建议使用质数来减少问题。让我们再次尝试将这些键存储在
HashMap中:
Map<Person, String> map = new HashMap();
map.put(person1, "1");
map.put(person2, "2");
person1.hashCode()和
person2.hashCode()是相同的。假设它们是 0。HashMap
将转到段 0,
LinkedList在其中将person1保存为值为“1”的键。第二种情况,当
HashMap再次到桶0存储key为“2”
的person2时,会发现那里已经存在另一个与它相等的key。这样它将覆盖以前的密钥。并且只有关键的
person2才会存在于我们的
HashMap中。
这就是我们了解HashMap规则的工作原理的方式,该规则规定不能使用多个相同的键!
但是,请记住,不相等的实例可以具有相同的哈希码,并且相等的实例必须返回相同的哈希码。
GO TO FULL VERSION