hashCode()
与 equals()
:集合背后的爱情故事
各位看官,今天咱们不聊风花雪月,也不谈柴米油盐,咱们聊聊Java世界里一对相爱相杀,又不可或缺的好基友:hashCode()
和 equals()
。 这俩哥们,在集合的世界里,那可是扛把子的存在,没有他们,集合就只能是一盘散沙,毫无秩序可言。
故事的开端:集合的烦恼
话说在很久很久以前(其实也就Java诞生那会儿),人们需要一种容器来存放各种各样的数据。 于是,数组应运而生。 数组这玩意儿,简单粗暴,直接用索引访问,效率也高。 但是,数组有个致命的缺点:大小固定。 这就像住在一个固定大小的房子里,东西多了就没地儿放,想搬家又麻烦。
为了解决这个问题,集合框架闪亮登场! ArrayList、LinkedList、HashSet、HashMap…各种各样的集合就像雨后春笋般冒了出来。 这些集合,大小可变,功能强大,简直是程序员的福音。
但是,问题也来了。 如何判断集合里是否已经存在某个元素? 如何快速找到某个元素? 这就涉及到元素之间的比较问题。
equals()
:你是我的唯一
equals()
方法,就是用来判断两个对象是否“相等”的。 注意,这里的“相等”和我们平时说的相等不太一样。 在Java的世界里,对象是引用类型,==
比较的是两个对象的引用是否指向同一个内存地址。 如果两个对象的内容一样,但是引用不同,==
会返回 false
。
equals()
方法,允许我们自定义对象相等的标准。 默认情况下,equals()
方法和 ==
的行为是一样的,比较的是引用。 但是,我们可以重写 equals()
方法,根据对象的属性来判断是否相等。
举个栗子:
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return 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 EqualsExample {
public static void main(String[] args) {
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
Person p3 = new Person("李四", 22);
System.out.println("p1 == p2: " + (p1 == p2)); // false
System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
System.out.println("p1.equals(p3): " + p1.equals(p3)); // false
}
}
在这个例子中,我们重写了 Person
类的 equals()
方法。 我们认为,如果两个 Person
对象的姓名和年龄都相同,那么这两个对象就是相等的。 因此,p1.equals(p2)
返回 true
,而 p1 == p2
返回 false
。
hashCode()
:快速定位的小助手
有了 equals()
方法,我们可以判断两个对象是否相等了。 但是,如果要在集合中查找某个元素,难道要遍历整个集合,逐个比较 equals()
吗? 如果集合很大,那效率也太低了!
这时候,hashCode()
方法就派上用场了。 hashCode()
方法返回一个对象的哈希码,哈希码是一个整数,可以用来快速定位对象在集合中的位置。
想象一下,你有一个巨大的图书馆,里面堆满了书。 你要找一本特定的书,如果一本一本的找,那得找到猴年马月。 但是,如果你给每本书都贴上一个标签(哈希码),然后把书按照标签分类存放,那么你就可以很快找到你要的书了。
hashCode()
方法的作用就是给对象贴标签。 不同的对象,哈希码可能相同,也可能不同。 但是,如果两个对象相等(equals()
返回 true
),那么它们的哈希码必须相同。 这是 hashCode()
方法和 equals()
方法之间最重要的约定。
hashCode()
和 equals()
的爱情故事:相辅相成,缺一不可
hashCode()
和 equals()
方法就像一对情侣,相辅相成,缺一不可。 如果只重写了 equals()
方法,而没有重写 hashCode()
方法,那么在使用某些集合(比如 HashSet、HashMap)时,可能会出现意想不到的问题。
举个栗子:
import java.util.HashSet;
import java.util.Set;
public class HashCodeExample {
public static void main(String[] args) {
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
Set<Person> personSet = new HashSet<>();
personSet.add(p1);
personSet.add(p2);
System.out.println("personSet size: " + personSet.size()); // 2
System.out.println("personSet contains p1: " + personSet.contains(p1)); // true
System.out.println("personSet contains p2: " + personSet.contains(p2)); // false
}
}
在这个例子中,我们创建了两个 Person
对象 p1
和 p2
,它们的姓名和年龄都相同,所以 p1.equals(p2)
返回 true
。 但是,由于我们没有重写 hashCode()
方法,所以 p1
和 p2
的哈希码很可能不同。
当我们把 p1
和 p2
添加到 HashSet
中时,HashSet
会根据哈希码把它们放到不同的位置。 结果,HashSet
中就出现了两个“相等”的对象,这违反了 HashSet
的特性:不允许重复元素。
更糟糕的是,当我们使用 personSet.contains(p2)
来判断 HashSet
中是否包含 p2
时,HashSet
会先计算 p2
的哈希码,然后根据哈希码找到对应的位置,再比较该位置上的元素和 p2
是否相等。 由于 p2
的哈希码和 p1
的哈希码不同,所以 HashSet
认为 p2
不存在。
为了解决这个问题,我们需要同时重写 hashCode()
方法和 equals()
方法。
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return 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);
}
}
import java.util.HashSet;
import java.util.Set;
public class HashCodeExample {
public static void main(String[] args) {
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
Set<Person> personSet = new HashSet<>();
personSet.add(p1);
personSet.add(p2);
System.out.println("personSet size: " + personSet.size()); // 1
System.out.println("personSet contains p1: " + personSet.contains(p1)); // true
System.out.println("personSet contains p2: " + personSet.contains(p2)); // true
}
}
在这个例子中,我们重写了 Person
类的 hashCode()
方法,使用了 Objects.hash()
方法来生成哈希码。 Objects.hash()
方法会根据对象的属性生成哈希码,保证了如果两个对象的属性相同,那么它们的哈希码也相同。
现在,当我们把 p1
和 p2
添加到 HashSet
中时,HashSet
会根据哈希码把它们放到同一个位置。 由于 p1.equals(p2)
返回 true
,所以 HashSet
会认为 p2
和 p1
是同一个对象,不会重复添加。
hashCode()
和 equals()
的约定
为了保证集合的正确性,hashCode()
方法和 equals()
方法必须遵守以下约定:
- 一致性: 如果两个对象相等(
equals()
返回true
),那么它们的哈希码必须相同。 - 对称性: 如果
a.equals(b)
返回true
,那么b.equals(a)
必须返回true
。 - 传递性: 如果
a.equals(b)
返回true
,并且b.equals(c)
返回true
,那么a.equals(c)
必须返回true
。 - 非空性:
a.equals(null)
必须返回false
。 - 一致性(
hashCode()
): 如果对象的属性没有改变,那么多次调用hashCode()
方法必须返回相同的哈希码。
哪些集合需要重写 hashCode()
和 equals()
?
主要是一些基于哈希表的集合,比如:
HashSet
HashMap
LinkedHashSet
LinkedHashMap
这些集合使用哈希码来快速定位元素,因此,如果你的自定义对象要存储在这些集合中,就必须重写 hashCode()
和 equals()
方法。
如何正确重写 hashCode()
和 equals()
?
重写 hashCode()
和 equals()
方法看似简单,但实际上有很多坑。 下面是一些建议:
- 使用 IDE 自动生成: 大多数 IDE(比如 IntelliJ IDEA、Eclipse)都提供了自动生成
hashCode()
和equals()
方法的功能。 这可以避免很多手误。 - 使用
Objects.equals()
和Objects.hash()
:Objects.equals()
方法可以避免空指针异常,Objects.hash()
方法可以方便地生成哈希码。 - 考虑所有重要的属性: 在重写
equals()
方法时,要考虑所有影响对象相等性的属性。 在重写hashCode()
方法时,也要使用所有这些属性来生成哈希码。 - 保持一致性: 确保
hashCode()
方法和equals()
方法保持一致。 如果两个对象相等,那么它们的哈希码必须相同。
总结:
hashCode()
和 equals()
方法是Java集合框架中非常重要的两个方法。 它们就像一对情侣,相辅相成,缺一不可。 只有正确地重写了这两个方法,才能保证集合的正确性和效率。
下面用一个表格总结一下:
方法 | 作用 | 场景 | 是否需要重写 |
---|---|---|---|
equals() |
用于判断两个对象是否相等。 默认比较的是对象的引用,可以通过重写来比较对象的属性。 | 当需要自定义对象相等的标准时,比如在集合中判断元素是否重复。 | 是 |
hashCode() |
用于生成对象的哈希码,哈希码是一个整数,可以用来快速定位对象在集合中的位置。 如果两个对象相等(equals() 返回 true ),那么它们的哈希码必须相同。 |
当对象需要存储在基于哈希表的集合中时,比如 HashSet 、HashMap 。 |
是 |
== |
用于比较两个对象的引用是否指向同一个内存地址。 | 通常用于比较基本类型的值,或者判断两个对象是否是同一个对象。 | 否 |
最后的忠告:
重写 hashCode()
和 equals()
方法是一项重要的任务,需要认真对待。 不要为了偷懒而随便重写,否则可能会导致意想不到的问题。 记住,代码质量的提高,往往就在这些细节之中。
希望这篇文章能够帮助你更好地理解 hashCode()
和 equals()
方法,并在实际开发中正确使用它们。 祝你编程愉快!