引言:
1.图书馆如何快速找到《Java编程思想》这本书 ?
2.相同的两本《Java编程思想》书,如何找到它 ?
核心比喻:图书馆和索书号
想象一个巨大的图书馆(这就是 HashMap 或 HashSet)。
-
没有 hashCode 的情况(只用 equals):
-
你要找一本叫《Java编程思想》的书。
-
管理员只能从第一排书架开始,一本一本地拿起来看封面,对比书名是否等于《Java编程思想》。
-
直到找到为止。效率极低,这是
O(n)的时间复杂度。
-
-
有 hashCode 的情况:
-
你要找同一本书。
-
这次,书籍都有一个 索书号(这就是 hashCode),比如
747.5。这个索书号是通过一个特定算法(哈希函数)根据书名、作者等信息计算出来的。 -
管理员直接根据
747.5这个索书号,走到标有747的书架区,然后很快在那一小堆书里找到编号为.5的那本。 -
效率极高,因为管理员直接定位到了大致区域,只需要在极少数书籍(一个“桶”或“篮子”里)中进行精细比较。这接近
O(1)的时间复杂度。
-
结论:hashCode 的核心目的就是为了“快速定位”,它是给哈希表这种数据结构使用的“索引”或“地址”。
为什么 hashCode 必须和 equals 一起工作?
现在我们来解释那个著名的“契约”。继续用图书馆的例子:
-
hashCode 契约第一条:如果两本书的索书号不同,它们肯定不是同一本书。
-
这很合理,书都放在不同的架子上,当然不是同一本。
-
-
hashCode 契约第二条(最关键):如果两本书是同一本书(equals 为 true),它们的索书号必须相同。
-
想象一下,图书馆里有两本完全相同的《Java编程思想》(假设它们是相等的)。如果一本的索书号是
747.5,另一本是328.1,会发生什么? -
你用《Java编程思想》计算出的索书号是
747.5,然后去747书架找,只找到了其中一本。另一本你永远也找不到,因为它被放在了 328 书架! 这彻底破坏了哈希表的逻辑。
-
精细比较(equals)成本很高,所以先用“快速定位”(hashCode)缩小范围。定位到同一个“桶”之后,再用 equals 进行最终确认。
一个会出错的代码示例
public class Student {
private String id;
private String name;
// 假设我们只重写了 equals,认为id和name相同就是同一个学生
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(id, student.id) && Objects.equals(name, student.name);
}
// 但是忘记了重写 hashCode()
// 那么将使用从Object类继承的默认实现,该实现通常根据内存地址生成一个不同的整数。
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student("001", "Alice");
Student s2 = new Student("001", "Alice"); // 在逻辑上,s1 等于 s2
HashMap<Student, String> map = new HashMap<>();
map.put(s1, "优秀");
// 现在尝试用“相等”的s2对象去取值
String grade = map.get(s2);
System.out.println(grade); // 输出:null !!!
}
}
为什么会输出 null?
-
map.put(s1, "优秀"):HashMap 调用s1.hashCode()(默认实现)得到一个值,比如12345。它将<s1, "优秀">这个键值对存放在编号为12345的桶里。 -
map.get(s2):HashMap 调用s2.hashCode()(默认实现)得到另一个值,比如67890。因为67890 != 12345,HashMap 会直接去67890这个桶里找,而那个桶是空的!它根本不会调用equals方法去比较s1和s2。
修复方法: 在 Student 类中同时重写 hashCode。
java
@Override
public int hashCode() {
// 使用和equals方法相同的字段来计算哈希码
return Objects.hash(id, name);
}
现在,s1 和 s2 的 hashCode() 返回值相同了。当 map.get(s2) 时,HashMap 会定位到正确的桶,然后在那个桶里调用 equals 方法进行精确比较,最终成功找到 s1 对应的值 "优秀"。
总结
-
为什么要有 hashCode?
-
为了速度。它让基于哈希的集合(如
HashMap,HashSet) 能够通过一个整数快速定位到数据的可能存储位置,避免遍历整个集合。
-
-
为什么 hashCode 必须和 equals 一致?
-
为了保证哈希表的正确性。如果两个相等的对象拥有不同的哈希码,它们就会被放入哈希表的不同位置,导致你永远无法用另一个“相等”的对象找到它。这破坏了哈希表最根本的查找功能。
-
简单说:hashCode 是用来快速找“大概在哪”,equals 是用来最终确认“是不是它”。两者必须配合,才能既快又准。

被折叠的 条评论
为什么被折叠?



