equals和hashCode异同

equals()和hashcode()

简单介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 返回对象的hashCode值
* 备注:
* 只要对象的equals内容未被改变,多次调用此方法必须返回相同的整型
* 若两个对象调用equals后返回true,则此方法必须返回相同的整型
* 为equals判断不相等的对象返回不同的hashcode不必须,但实现了后可以提高性能
*/
@HotSpotIntrinsicCandidate
public native int hashCode();

public boolean equals(Object obj) {
return (this == obj);
}

  hashcode()返回对象的哈希码值,符合一致性,同一对象无论多少次调用执行此方法都会返回相同整型结果,但hashcode()只用在散列表等类中。

  equals()是基础类Object的成员函数,所以每个类都会继承此方法。其符合反射性,对称性,及物性,一致性。

  最好的情况就是:如果两个对象相等,即equals(Object)返回真,分别调用hashcode()必须返回相同整型结果,若两个对象不相等,则hashcode()返回不同的整型结果。

  但我们总说:equals相等则hashcode必定相等,hashcode相等equals未必相等


问题解答

问题1:默认的equals()与==有什么异同?

  == 比较对象的引用地址是否相同,即是否为同一对象。equals()在没有被重写时等价于==,重写后根据覆盖的方法来判断是否相等。

那么equals()与hashcode()有什么异同?

  首先对于不创建散列表的类,二者没有关系,先看一下以下代码示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
public class Student {
private String id;
private String name;

public Student(String id, String name) {
this.id = id;
this.name = name;
}

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

@Override
public boolean equals(Object obj) {
if(obj == null)
return false;
if(this == obj)
return true;
if(this.getClass() != obj.getClass())
return false;
Student student = (Student) obj;
return this.name.equals(student.getName());
}
}

public class Test {
public static void main(String[] args){
Student s1 = new Student("001","张三");
Student s2 = new Student("001","张三");
Student s3 = new Student("002","张三");
Student s4 = new Student("003","李四");
System.out.println("s1.equals(s2): " + s1.equals(s2));
System.out.println("s1.equals(s3): " + s1.equals(s3));
System.out.println("s1.equals(s4): " + s1.equals(s4));
System.out.println("s1.hashCode(): " + s1.hashCode());
System.out.println("s2.hashCode(): " + s2.hashCode());
System.out.println("s3.hashCode(): " + s3.hashCode());
System.out.println("s4.hashCode(): " + s4.hashCode());
}
}

  结果输出如下,普通对象使用时进行比较不会使用hashCode(),而equals()则根据具体的实现来确定返回结果。

1
2
3
4
5
6
7
s1.equals(s2): true
s1.equals(s3): true
s1.equals(s4): false
s1.hashCode(): 2088051243
s2.hashCode(): 1277181601
s3.hashCode(): 41903949
s4.hashCode(): 488970385

  那么对于会创建类对应的散列表的对象呢?继续看以下的代码示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Test {
public static void main(String[] args){
Student s1 = new Student("001","张三");
Student s2 = new Student("001","张三");
Student s3 = new Student("002","张三");
Student s4 = new Student("003","李四");
System.out.println("s1.equals(s2): " + s1.equals(s2));
System.out.println("s1.equals(s3): " + s1.equals(s3));
System.out.println("s1.equals(s4): " + s1.equals(s4));
System.out.println("s1: " + s1.toString() + "s1.hashCode(): " + s1.hashCode());
System.out.println("s2: " + s2.toString() + "s2.hashCode(): " + s2.hashCode());
System.out.println("s3: " + s3.toString() + "s3.hashCode(): " + s3.hashCode());
System.out.println("s4: " + s4.toString() + "s4.hashCode(): " + s4.hashCode());

HashSet set = new HashSet();
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
System.out.println("HashSet: " + set.toString());
}
}

  结果打印如下,可以看到Set中会有“重复”元素存在,因为我们只重写了equals(),而Set集合判断元素相等还需要哈希值相等,所以需要重写hashCode()

1
2
3
4
5
6
7
8
s1.equals(s2): true
s1.equals(s3): true
s1.equals(s4): false
s1: equals.Student@7c75222bs1.hashCode(): 2088051243
s2: equals.Student@7a0ac6e3s2.hashCode(): 2047526627
s3: equals.Student@71be98f5s3.hashCode(): 1908316405
s4: equals.Student@6fadae5ds4.hashCode(): 1873653341
HashSet: [equals.Student@6fadae5d, equals.Student@7a0ac6e3, equals.Student@71be98f5, equals.Student@7c75222b]

  简单重写hashCode如下。

1
2
3
4
@Override
public int hashCode() {
return this.name.toUpperCase().hashCode();
}

  结果打印如下,具有相等哈希码值的对象不会再重复的存入Set了。

1
2
3
4
5
6
7
8
s1.equals(s2): true
s1.equals(s3): true
s1.equals(s4): false
s1: equals.Student@bd2e9s1.hashCode(): 774889
s2: equals.Student@bd2e9s2.hashCode(): 774889
s3: equals.Student@bd2e9s3.hashCode(): 774889
s4: equals.Student@cd94ds4.hashCode(): 842061
HashSet: [equals.Student@cd94d, equals.Student@bd2e9]

  所以我们可以总结:对于哈希类,若两个对象相等,即equals()为真,则hashCode()一定相等,反之则不一定。因为hash公式不同的实现可能会导致不同的对象算出哈希值也相等(哈希碰撞),对于测试代码,因为实现的equals只比较name是否等值,而hashcode只比较name值对应的哈希码是否相等,所以两者应该是同步的。

  对于HashSet为了优化速度,可以利用上述总结,判断元素是否重复时,先比较哈希值,当哈希值不同肯定不重复,哈希值相同再用equals()比较是否是相同元素,当equals()为false存入相同位置,所以每个哈希表中元素应该是由链式结构存储。

  HashMap部分源代码如下,获取元素时是通过哈希码来查找的,再对链表的元素依次判断equals直到找到元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
}

static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

参考博客:

https://www.cnblogs.com/skywang12345/p/3324958.html

因博客主未标明不可转载,若内容涉及侵权请及时告知,我会尽快修改和删除相关内容