为什么覆写equals的时候一定要覆写hashCode?

2023-12-06 14:48
文章标签 一定 hashcode equals 覆写

本文主要是介绍为什么覆写equals的时候一定要覆写hashCode?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

经常在论坛上面看到覆写hashCode函数的问题,很多情况下是一些开发者不了解hash code,或者和equals一起用的时候不太清楚为啥一定要覆写hashCode。


对于hash code的理论我不想多说,这个话题太大。我只想说用hash code的原因只有一个:效率。理论的说法它的复杂度只有O(1)试想我们把元素放在线性表里面,每次要找一个元素必须从头一个一个的找,复杂度有O(n)。如果放在平衡二叉树,复杂度也有O(log n)。


为啥很多地方说“覆写equals的时候一定要覆写hashCode”。
说到这里我知道很多人知道有个原则:
如果a.equals(b)那么要确保a.hashCode()==b.hashCode()。
为什么?hashCode和我写的程序的业务逻辑毫无关系,为啥我要override?
要我说如果你的class永远不可能放在hash code为基础的容器内,不必劳神,您真的不必override hashCode() :)

说得准确一点放在HashMap和Hashtable里面如果是作为value而不是作为key的话也是不必override hashCode了。至于HashSet,实际上它只是忽略value的HashMap,每次HashSet.add(o)其实就是HashMap.put(o, dummyObject)。

那为什么放到Hash容器里面要overide hashCode呢?
因为每次get的时候HashMap既要看equals是不是true也要看hash code是不是一致,put的时候也是要看equals和hash code。

 

如果说到这里您还是不太明白,咱就举个例子:

譬如把一个自己定义的class Foo{...}放到HashMap。实际上HashMap也是把数据存在一个数组里面,所以在put函数里面,HashMap会调Foo.hashCode()算出作为这个元素在数组里面的下标,然后把key和value封装成一个对象放到数组。
等一下,万一两个对象算出来的hash code一样怎么办?会不会冲掉?
先回答第2个问题,会不会冲掉就要看Foo.equals()了,如果equals()也是true那就要冲掉了。万一是false,就是所谓的collision了。当两个元素hashCode一样但是equals为false的时候,那个HashMap里面的数组的这个元素就变成了链表。也就是hash code一样的元素在一个链表里面,链表的头在那个数组里面。

回过来说get的时候,HashMap也先调key.hashCode()算出数组下标,然后看equals是不是true,所以就涉及了equals。


反观假设如果a.equals(b)但是a.hashCode()!=b.hashCode()的话,在put元素a之后,我们又用一个a.equals(b)但是b.hashCode()!=a.hashCode()的b元素作为key来get的时候就找不到a了。如果a.hashCode()==b.hashCode()但是!a.equals(b)倒是不要紧,这2个元素会collision然后被放到链表,只是效率变差。

这里有个非常简化版的HashMap实现帮助大家理解。

 

Java代码 复制代码
  1. /*  
  2. * Just to demonstrate hash map mechanism,   
  3. * Please do not use it in your commercial product. 
  4. *  
  5. */  
  6. public class SimpleHashMap {   
  7.     ArrayList<LinkedList<Entry>> entries = new ArrayList<LinkedList<Entry>>();   
  8.        
  9.     /**  
  10.     * Each key-value is encapsulated by Entry.  
  11.     */  
  12.     static class Entry {   
  13.         Object key;   
  14.         Object value;   
  15.         public Entry(Object key, Object value) {   
  16.             this.key = key;   
  17.             this.value = value;   
  18.        }   
  19.     }   
  20.        
  21.     void put(Object key, Object value) {   
  22.         LinkedList<Entry> e = entries.get(key.hashCode());   
  23.         if (e != null) {   
  24.             for (Entry entry : e) {   
  25.                 if (entry.key.equals(key)) {   
  26.                     entry.value = value;// Match in lined list  
  27.                     return;   
  28.                 }   
  29.             }   
  30.             e.addFirst(new Entry(key, value));// Add the entry to the list  
  31.         } else {   
  32.             // Put the new entry in array   
  33.             LinkedList<Entry> newEntry = new LinkedList<Entry>();   
  34.             newEntry.add(new Entry(key, value));   
  35.             entries.add(key.hashCode(), newEntry);   
  36.         }   
  37.     }   
  38.        
  39.     Object get(Object key) {   
  40.        LinkedList<Entry> e = entries.get(key.hashCode());   
  41.        if (e != null) {   
  42.             for (Entry entry : e) {   
  43.                 if (entry.key.equals(key)) {   
  44.                     return entry.value;   
  45.                 }   
  46.             }   
  47.        }   
  48.        return null;   
  49.     }   
  50.   
  51.     /**  
  52.     * Do we need to override equals() and hashCode() for SimpleHashMap itself?  
  53.     * I don't know either:)  
  54.     */  
  55. }  
/*
* Just to demonstrate hash map mechanism, 
* Please do not use it in your commercial product.
*
*/
public class SimpleHashMap {
ArrayList<LinkedList<Entry>> entries = new ArrayList<LinkedList<Entry>>();
/**
* Each key-value is encapsulated by Entry.
*/
static class Entry {
Object key;
Object value;
public Entry(Object key, Object value) {
this.key = key;
this.value = value;
}
}
void put(Object key, Object value) {
LinkedList<Entry> e = entries.get(key.hashCode());
if (e != null) {
for (Entry entry : e) {
if (entry.key.equals(key)) {
entry.value = value;// Match in lined list
return;
}
}
e.addFirst(new Entry(key, value));// Add the entry to the list
} else {
// Put the new entry in array
LinkedList<Entry> newEntry = new LinkedList<Entry>();
newEntry.add(new Entry(key, value));
entries.add(key.hashCode(), newEntry);
}
}
Object get(Object key) {
LinkedList<Entry> e = entries.get(key.hashCode());
if (e != null) {
for (Entry entry : e) {
if (entry.key.equals(key)) {
return entry.value;
}
}
}
return null;
}
/**
* Do we need to override equals() and hashCode() for SimpleHashMap itself? 
* I don't know either:)
*/
}

 

这个问题的权威阐释可以参考Bloch的<Effective Java>的 Item 9: Always overridehashCode when you overrideequals

这篇关于为什么覆写equals的时候一定要覆写hashCode?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/462216

相关文章

跟我一起玩《linux内核设计的艺术》第1章(四)——from setup.s to head.s,这回一定让main滚出来!(已解封)

看到书上1.3的大标题,以为马上就要见着main了,其实啊,还早着呢,光看setup.s和head.s的代码量就知道,跟bootsect.s没有可比性,真多……这确实需要包括我在内的大家多一些耐心,相信见着main后,大家的信心和干劲会上一个台阶,加油! 既然上篇已经玩转gdb,接下来的讲解肯定是边调试边分析书上的内容,纯理论讲解其实我并不在行。 setup.s: 目标:争取把setup.

重写equals和hashCode的原则规范

当符合以下条件时不需要重写equals方法:     1.     一个类的每一个实例本质上都是唯一的。     2.     不关心一个类是否提供了“逻辑相等”的测试功能     3.     超类已经改写了equals方法,并且从超类继承过来的行为对于子类也是合适的。     4.     一个类时私有的或者是package私有的,并且可以确定它的equals方法永远不会被调用。(这

Subarray Sum Equals K

Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k. Example 1: Input:nums = [1,1,1], k = 2Output: 2 思路:用prefixsum prefixsu

你一定不知道的10个Python读写文件的高效技巧!

在Python中高效地读写文件是日常编程任务中的一项重要技能。这里将详细介绍10个高效的Python文件读写技巧,涵盖基本的文件操作到高级技巧。 1. 使用with语句管理文件 使用with语句可以确保文件被正确关闭,即使在读写文件时发生异常也是如此。with语句会自动管理文件的上下文,包括文件的打开和关闭。 with open('example.txt', 'r') as file:con

学历不会改变命运但知识一定可以改变命运

一、知识与学历的区别 首先,我们需要区分“知识”与“学历”。学历通常是指一个人通过正规教育体系获得的证书或学位,而知识则是更为宽泛的概念,它包括了一个人通过各种途径获得的信息、技能和理解。学历可能只是知识的一部分,而真正的知识应该是全面的,它不仅仅局限于书本和课堂。 二、知识如何改变命运 提升个人素质:知识的积累能够提升个人素质,包括思维能力、判断力和创造力。这些都是在现代社会中取得成功

Java HasCode equals == 的区别

== 用来判断两个值,或者两个对象的内存地址是否一样。 equals equals 方法用来判断两个对象是否相等。equals 是Object 类的方法,默认情况下,比较两个对象是否是同一个对象,内部通过 == 实现。如果想比较两个对象的其他内容,则可以通过重写equals 方法。比如String 就重写了equals 方法。 equals是Object类的方法,默认情况下比较两个对象是否

有理有据!为什么String选择数字31作为hashCode方法乘子?

点击上方“朱小厮的博客”,选择“设为星标” 回复”1024“获取独家整理的学习资料 来源:http://1t.click/Xkk 1. 背景 某天,我在写代码的时候,无意中点开了 String hashCode 方法。然后大致看了一下 hashCode 的实现,发现并不是很复杂。但是我从源码中发现了一个奇怪的数字,也就是本文的主角31。这个数字居然不是用常量声明的,所以没法从字面意思上推断

【编程底层思考】线程阻塞时一定会释放cpu吗

线程阻塞时是否释放CPU取决于阻塞的原因和操作系统的行为。以下是一些具体情况: 1. 阻塞等待资源:当线程因为等待某个资源(如锁、信号量、条件变量等)而阻塞时,它通常会释放CPU,以便其他线程可以运行。在这种情况下,阻塞的线程不会占用CPU资源,直到它等待的资源变得可用。 2. 阻塞等待I/O操作:当线程因为等待I/O操作(如读取文件、网络通信等)而阻塞时,它也会释放CPU。操作系统会将线程挂

连接oracle的应用达到一定数量其他人就无法连接的原因以及解决办法

根本原因就是Oracle的process和session已经达到了甚至超过了最大值,解决办法如下: 查看process和session的参数和占用值: show parameter processes; select count() from v p r o c e s s ; s e l e c t c o u n t ( ∗ ) f r o m v process; select count(

Java面试题:equals和==的区别与联系分别是什么?

1. ==运算符 ==是一个运算符,其用于比较两个变量的内存地址是否相等;对于基本数据类型(int、char、Boolean等),==比较的是它们的值;而对于引用数据类型的话(String、Object、ArrayList等),==比较的是引用,也就是对象在内存中的地址,即检查两个引用是否指向堆内存中的同一个对象实例。 代码举例: public class Main {public stat