重写equals方法和重写hashcode方法没有必然因果关系

看到很多博客把标题设置成 "为什么重写equals方法后一定要重写hashcode方法",虽然这是个面试题,事实上,若不看情况地将该命题一概而论,很容易造成不必要的误解。上述标题易把equals和hashCode之间形成因果关系,equals是因,hashCode是果。然而看过源码的都应该知道,Object的equals方法默认是返回 this == otherObj,而hashCode是一个native本地方法,由底层的c++实现,是完全独立的两个方法,没有必然的因果关系。

要讨论两者的关系,离不开无序不重复集合,比如HashSet,HashMap等等,一个较好的例子是在无序不重复集合中存取自定义对象:(设有自定义对象Person,有字段name,age等,假设业务要求name和age都相等才算两个Person相等)

首先大致预备下基础知识

  • HashMap和HashSet等是如何存数据的?简单讲分两步,第一步,通过对象person的哈希值得到逻辑地址A。第二步,若地址A上没其他对象,则存入成功,否则使用链表(jdk1.7)或者红黑树(jdk1.8)处理散列冲突,如何处理?以A为起点一个个遍历查找挂在A上的链表或者二分查找挂在A上的红黑树,若链表或红黑树上每个节点都不等于person(调用equals比较),则把person挂上去,也就是put成功,否则找到相等的节点,用person更新它的值

比如HashSet想存一个Person,它首先得知道这个Person放哪里,于是会调用Person的hashCode方法返回一个hash值,并和length - 1相与生成它的逻辑地址,如下图。如果幸运,地址没冲突,HashSet就确定把Person放这里了

  • 像Integer、String、Byte、Boolean等对象,java已经重写了对应的hashCode方法,使得值相同的不同堆对象的哈希值仍相同,自定义对象调用的是Object的hashCode方法,也就是c++实现的本地方法,只要是不同的堆对象,哈希值就不会相同

了解完基础知识后不难发现,

之所以要重写hashCode是为了保证多个相等的Person堆对象的哈希值也相等

若不重写hashCode,Person调用的就是c++实现的本地方法,即使你两个new出来的Person对象name和age都相同,但由于是两个不同的堆对象,c++的那套代码会hash出不同的结果,所以逻辑地址也就不同,HashMap看到是不同的逻辑地址就会把两者存到不同地方,于是一个HashMap里出现两个一样的Person,第一,与红字业务冲突,第二,与无序不重复集合这个定义冲突,第三,你在get操作的时候,它不知道get哪一个给你

之所以要重写equals,一是为了业务逻辑,即自定义对象要怎样比较才算相等,毕竟不止一个字段。二是同样为了保证在HashMap或HashSet中不出现重复的自定义对象

若不重写equals,则HashMap或HashSet在遍历链表或红黑树处理冲突时就不知道怎么比较Person对象才算相同,只能调用Object的equals方法,使用 == 比较引用地址,导致name和age都相等的Person仍会被判定为不相同,同样与红字业务冲突。再者,假设你已经重写了正确的hashCode方法,即确保了name和age相同的Person堆对象都会被hash到同一逻辑地址A,此时假设来了三个name和age一模一样的Person,第二个Person被映射到A后发现已经有对象了,于是尝试解决哈希冲突,调用equals比较,通过 == 比较后发现不相等,HashMap松了口气,直接开心地把第二个Person挂到了A的链表或红黑树上,这样,HashMap又出现了两个一样的Person!!于是你发现,重写hashCode后一定还要重写equals方法

由此可见,重写hashCode和重写equals方法之间没有必然的因果关系,就绿色字体而言,那标题是不是又可以改成 "为什么重写hashCode后还一定要重写equals?" 呢?对于HashMap的内部逻辑,hashCode和equals本身就是一种对等的关系,缺一不可。一个更好的问法应当是:"为什么在编程的时候有时会需要重写hashCode和equals方法?"

发布了37 篇原创文章 · 获赞 42 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/qq_37960007/article/details/104216478