先以一段代码开始这篇blog。
03 |
private String first; //first name
|
04 |
private String last; //last name
|
06 |
public String getFirst() {
|
10 |
public void setFirst(String first) {
|
14 |
public String getLast() {
|
18 |
public void setLast(String last) {
|
22 |
public Name(String first, String last) {
|
28 |
public boolean equals(Object object) {
|
29 |
Name name = (Name) object;
|
31 |
return first.equals(name.getFirst()) && last.equals(name.getLast());
|
34 |
public static void main(String[] args) {
|
35 |
Map<Name, String> map = new HashMap<Name, String>();
|
36 |
map.put( new Name( "mali" , "sb" ), "yes" );
|
38 |
System.out.println( "is the key existed? ture or false? -> "
|
39 |
+ map.containsKey( new Name( "mali" , "sb" )));
|
那输出结果是什么呢?类似这样的题目总能遇到,以前不知道有什么好考的,弱智?自己动手尝试了一次,发现结果不是自己想象的那样。本篇就用来揭示HashMap的equals与hashCode中你不知道的秘密。结果如下:
is the key existed? ture or false? -> false
对的,结果就是false,我很不理解为什么这样,已经重写了equals函数了啊!当时真心不服气,就在equals函数里面打了断点,然后更让我难以置信的事情发生了,断点处没有停。非常困惑,不过还好,jdk的源码在手上,去查了HashMap中containsKey函数的源码。源码结构如下图:

从图中可以看出,真正干活的是getEntry(Object key),重点看如下两行:
2 |
((k = e.key) == key || (key != null && key.equals(k))))
|
从if条件上看,是一个短路与,首先要判断两个对象的hash值是否相等。如果相等才进行后续的判断。或者换一个说法,在HashMap中只有两个对象的hash值相等的前提下才会执行equals方法的逻辑。关于这一点,有两个佐证。
自己编程验证。
在文章开头的基础上,做了点儿改进,输出两个对象的hash值,并且在equals方法中打印一行文字。如下:
03 |
private String first; //first name
|
04 |
private String last; //last name
|
06 |
public String getFirst() {
|
10 |
public void setFirst(String first) {
|
14 |
public String getLast() {
|
18 |
public void setLast(String last) {
|
22 |
public Name(String first, String last) {
|
28 |
public boolean equals(Object object) {
|
29 |
System.out.println( "equals is running..." );
|
30 |
Name name = (Name) object;
|
32 |
return first.equals(name.getFirst()) && last.equals(name.getLast());
|
35 |
public static void main(String[] args) {
|
36 |
Map<Name, String> map = new HashMap<Name, String>();
|
37 |
Name n1 = new Name( "mali" , "sb" );
|
38 |
System.out.println( "the hashCode of n1 : " + n1.hashCode());
|
40 |
Name n2 = new Name( "mali" , "sb" );
|
41 |
System.out.println( "the hashCode of n2 : " + n2.hashCode());
|
42 |
System.out.println( "is the key existed? ture or false? -> "
|
43 |
+ map.containsKey(n2));
|
结果:
the hashCode of n1 : 1690552137
the hashCode of n2 : 1901116749
is the key existed? ture or false? -> false
从执行结果可以看出1、两个对象的hash值是不相同的;2、equals方法确实也没有执行。
再次对代码进行改进,加入重写的hashCode方法,如下,看看这次的结果会是怎样。
03 |
private String first; //first name
|
04 |
private String last; //last name
|
06 |
public String getFirst() {
|
10 |
public void setFirst(String first) {
|
14 |
public String getLast() {
|
18 |
public void setLast(String last) {
|
22 |
public Name(String first, String last) {
|
28 |
public boolean equals(Object object) {
|
29 |
System.out.println( "equals is running..." );
|
30 |
Name name = (Name) object;
|
32 |
return first.equals(name.getFirst()) && last.equals(name.getLast());
|
35 |
public int hashCode() {
|
36 |
System.out.println( "hashCode is running..." );
|
37 |
return first.hashCode() + last.hashCode();
|
40 |
public static void main(String[] args) {
|
41 |
Map<Name, String> map = new HashMap<Name, String>();
|
42 |
Name n1 = new Name( "mali" , "sb" );
|
43 |
System.out.println( "the hashCode of n1 : " + n1.hashCode());
|
45 |
Name n2 = new Name( "mali" , "sb" );
|
46 |
System.out.println( "the hashCode of n2 : " + n2.hashCode());
|
47 |
System.out.println( "is the key existed? ture or false? -> "
|
48 |
+ map.containsKey(n2));
|
结果:
hashCode is running...
the hashCode of n1 : 3347552
hashCode is running...
hashCode is running...
the hashCode of n2 : 3347552
hashCode is running...
equals is running...
is the key existed? ture or false? -> true
同样从结果中可以看出:在hash值相等的情况下,equals方法也执行了,HashMap的containsKey方法也像预想的那样起作用了。
结论:
在使用HashSet(contains也是调用HashMap中的方法)、HashMap等集合时,如果用到contains系列方法时,记得需同时重写equals与hashCode方法。