@@ -12,26 +12,29 @@ head:
12
12
content : Java,Java SE,Java基础,Java教程,Java程序员进阶之路,Java进阶之路,Java入门,教程,java,hashcode,equals
13
13
---
14
14
15
+ # 13.5 为什么重写equals的时候必须重写hashCode
16
+
15
17
“二哥,我在读《Effective Java》 的时候,第 11 条规约说重写 equals 的时候必须要重写 hashCode 方法,这是为什么呀?”三妹单刀直入地问。
16
18
17
19
“三妹啊,这个问题问得非常好,因为它也是面试中经常考的一个知识点。今天哥就带你来梳理一下。”我说。
18
20
19
- Java 是一门面向对象的编程语言,所有的类都会默认继承自 Object 类,而 Object 的中文意思就是“对象”。
21
+ Java 是一门面向对象的编程语言,所有的类都会默认继承自 [ Object 类] ( https://tobebetterjavaer.com/oo/object-class.html ) ,而 Object 的中文意思就是“对象”。
20
22
21
23
Object 类中有这么两个方法:
22
24
23
25
``` java
24
26
public native int hashCode();
25
27
26
28
public boolean equals(Object obj) {
27
- return (this == obj);
29
+ return (this == obj);
28
30
}
29
31
```
30
- 1)hashCode 方法
32
+
33
+ ** 1)hashCode 方法**
31
34
32
35
这是一个本地方法,用来返回对象的哈希值(一个整数)。在 Java 程序执行期间,对同一个对象多次调用该方法必须返回相同的哈希值。
33
36
34
- 2)equals 方法
37
+ ** 2)equals 方法**
35
38
36
39
对于任何非空引用 x 和 y,当且仅当 x 和 y 引用的是同一个对象时,equals 方法才返回 true。
37
40
@@ -41,13 +44,13 @@ public boolean equals(Object obj) {
41
44
42
45
第一,如果两个对象调用 equals 方法返回的结果为 true,那么两个对象调用 hashCode 方法返回的结果也必然相同——来自 hashCode 方法的 doc 文档。
43
46
44
- 第二,每当重写 equals 方法时, hashCode 方法也需要重写 ,以便维护上一条规约。
47
+ 第二,每当重写 [ equals 方法 ] ( https://tobebetterjavaer.com/string/equals.html ) 时, [ hashCode 方法 ] ( https://tobebetterjavaer.com/basic-extra-meal/hashcode.html ) 也需要重写 ,以便维护上一条规约。
45
48
46
49
“哦,这样讲的话,两个方法确实关联上了,但究竟是为什么呢?”三妹抛出了终极一问。
47
50
48
- “hashCode 方法的作用是用来获取哈希值,而该哈希值的作用是用来确定对象在哈希表中的索引位置 。”我说。
51
+ “hashCode 方法的作用是用来获取哈希值,而该哈希值的作用是用来确定 [ 对象在哈希表中的索引位置(HashMap 的时候会讲) ] ( https://tobebetterjavaer.com/collection/hashmap.html ) 。”我说。
49
52
50
- 哈希表的典型代表就是 HashMap,它存储的是键值对,能根据键快速地检索出对应的值。
53
+ 哈希表的典型代表就是 [ HashMap] ( https://tobebetterjavaer.com/collection/hashmap.html ) ,它存储的是键值对,能根据键快速地检索出对应的值。
51
54
52
55
``` java
53
56
public V get(Object key) {
@@ -61,21 +64,28 @@ public V get(Object key) {
61
64
``` java
62
65
final HashMap . Node<K ,V > getNode(int hash, Object key) {
63
66
HashMap . Node<K ,V > [] tab; HashMap . Node<K ,V > first, e; int n; K k;
67
+ // 判断 HashMap 的 table 是否为 null 以及 table 长度是否大于 0
64
68
if ((tab = table) != null && (n = tab. length) > 0 &&
69
+ // 根据 hash 值计算出在 table 中的索引位置,并获取第一个节点
65
70
(first = tab[(n - 1 ) & hash]) != null ) {
66
- if (first. hash == hash && // always check first node
71
+ // 判断第一个节点的 hash 值是否与给定 hash 相等,如果相等,则检查 key 是否相等
72
+ if (first. hash == hash &&
67
73
((k = first. key) == key || (key != null && key. equals(k))))
68
74
return first;
75
+ // 如果不相等,获取当前节点的下一个节点
69
76
if ((e = first. next) != null ) {
77
+ // 如果当前节点为 TreeNode 类型(红黑树),则调用 TreeNode 的 getTreeNode 方法查找
70
78
if (first instanceof HashMap . TreeNode )
71
79
return ((HashMap . TreeNode<K ,V > )first). getTreeNode(hash, key);
80
+ // 如果不是红黑树节点,遍历链表查找
72
81
do {
73
82
if (e. hash == hash &&
74
83
((k = e. key) == key || (key != null && key. equals(k))))
75
84
return e;
76
85
} while ((e = e. next) != null );
77
86
}
78
87
}
88
+ // 如果没有找到对应的节点,则返回 null
79
89
return null ;
80
90
}
81
91
```
@@ -90,15 +100,15 @@ HashMap 是通过拉链法来解决哈希冲突的,也就是如果发生哈希
90
100
91
101
“O(n) 和 O(1) 是什么呀?”三妹有些不解。
92
102
93
- “这是时间复杂度的一种表示方法 ,随后二哥专门给你讲一下。简单说一下 n 和 1 的意思,很显然,n 和 1 都代表的是代码执行的次数,假如数据规模为 n,n 就代表需要执行 n 次,1 就代表只需要执行一次。”我解释道。
103
+ “这是 [ 时间复杂度 ] ( https://tobebetterjavaer.com/collection/time-complexity.html ) 的一种表示方法 ,随后二哥专门给你讲一下。简单说一下 n 和 1 的意思,很显然,n 和 1 都代表的是代码执行的次数,假如数据规模为 n,n 就代表需要执行 n 次,1 就代表只需要执行一次。”我解释道。
94
104
95
105
“三妹,你想一下,如果没有哈希表,但又需要这样一个数据结构,它里面存放的数据是不允许重复的,该怎么办呢?”我问。
96
106
97
107
“要不使用 equals 方法进行逐个比较?”三妹有些不太确定。
98
108
99
109
“这种方法当然是可行的,就像 ` if ((e = first.next) != null) {} ` 子句中那样,但如果数据量特别特别大,性能就会很差,最好的解决方案还是 HashMap。”
100
110
101
- HashMap 本质上是通过数组实现的 ,当我们要从 HashMap 中获取某个值时,实际上是要获取数组中某个位置的元素,而位置是通过键来确定的。
111
+ HashMap 本质上是通过 [ 数组 ] ( https://tobebetterjavaer.com/array/array.html ) 实现的 ,当我们要从 HashMap 中获取某个值时,实际上是要获取数组中某个位置的元素,而位置是通过键来确定的。
102
112
103
113
``` java
104
114
public V put(K key, V value) {
@@ -109,16 +119,30 @@ public V put(K key, V value) {
109
119
这是 HashMap 的 put 方法,会将键值对放入到数组当中。它会调用 putVal 方法:
110
120
111
121
``` java
112
- final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
113
- boolean evict) {
114
- HashMap . Node<K ,V > [] tab; HashMap . Node<K ,V > p; int n, i;
115
- if ((tab = table) == null || (n = tab. length) == 0 )
116
- n = (tab = resize()). length;
117
- if ((p = tab[i = (n - 1 ) & hash]) == null )
118
- tab[i] = newNode(hash, key, value, null );
119
- else {
120
- // 拉链
122
+ final HashMap . Node<K ,V > getNode(int hash, Object key) {
123
+ HashMap . Node<K ,V > [] tab; HashMap . Node<K ,V > first, e; int n; K k;
124
+ // 判断 HashMap 的 table 是否为 null 以及 table 长度是否大于 0
125
+ if ((tab = table) != null && (n = tab. length) > 0 &&
126
+ // 根据 hash 值计算出在 table 中的索引位置,并获取第一个节点
127
+ (first = tab[(n - 1 ) & hash]) != null ) {
128
+ // 判断第一个节点的 hash 值是否与给定 hash 相等,如果相等,则检查 key 是否相等
129
+ if (first. hash == hash &&
130
+ ((k = first. key) == key || (key != null && key. equals(k))))
131
+ return first;
132
+ // 如果不相等,获取当前节点的下一个节点
133
+ if ((e = first. next) != null ) {
134
+ // 如果当前节点为 TreeNode 类型(红黑树),则调用 TreeNode 的 getTreeNode 方法查找
135
+ if (first instanceof HashMap . TreeNode )
136
+ return ((HashMap . TreeNode<K ,V > )first). getTreeNode(hash, key);
137
+ // 如果不是红黑树节点,遍历链表查找
138
+ do {
139
+ if (e. hash == hash &&
140
+ ((k = e. key) == key || (key != null && key. equals(k))))
141
+ return e;
142
+ } while ((e = e. next) != null );
143
+ }
121
144
}
145
+ // 如果没有找到对应的节点,则返回 null
122
146
return null ;
123
147
}
124
148
```
0 commit comments