关于重写equals同时重写hashcode
1.Object中equals方法和hashcode
public boolean equals(Object obj) {
return (this == obj);
}
public native int hashCode();
可以看出Object的equals是使用“==”实现的,即该对象的地址比较而不是内容
2.为什么重写equels需要重写hashcode
当对象的equals()方法被重写时,通常有必要重写 hashCode() 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。
(1)两个对象相等,hashcode一定相等
(2)两个对象不等,hashcode不一定不等
(3)hashcode相等,两个对象不一定相等
(4)hashcode不等,两个对象一定不等
3.HashMap中使用自定义类作为Key时,为何要重写HashCode和Equals方法
首先以Person类为键存入Hashmap中
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
}
main
public static void main(String[] args) {
HashMap<Person, String> map = new HashMap<Person, String>();
map.put(new Person("001"), "findingsea");
map.put(new Person("002"), "linyin");
map.put(new Person("003"), "henrylin");
map.put(new Person("003"), "findingsealy");
System.out.println(map.toString());
System.out.println(map.get(new Person("001")));
System.out.println(map.get(new Person("002")));
System.out.println(map.get(new Person("003")));
}
结果
{Person@6e4d4d5e=henrylin, Person@275cea3=findingsea, Person@15128ee5=findingsealy, Person@4513098=linyin}
null
null
null
我们可以看到,这里出现了两个问题:
- 在添加的过程中,我们将
key=new Person("003")
的键值对添加了两次,那么在期望中,HashMap
中应该只存在一对这样的键值对,因为key
(期望中)是相同的,所以不应该重复添加,第二次添加的value="findingsealy"
应该替换掉原先的value="henrylin"
。但是在输入中,我们发现期望中的情况并没有出现,而是在HashMap
同时存在了value="findingsealy"
和value="henrylin"
的两个键值对,并且它们的key
值还是不相同的,这显然是错误的。 - 在获取
value
值时,我们分别用三个Person
对象去查找,这三个对象和我们刚刚存入的三个key
值(在期望中)是相同的,但是查找出的却是三个null
值,这显然也是错误的。
那么,正确的方法其实在很多地方都是被描述过了,直接对Person
类进行修改,重写equals
和hashCode
方法,修改过后的Person
类如下:
public class Person {
private String id;
public Person(String id) {
this.id = id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
if (id != null ? !id.equals(person.id) : person.id != null) return false;
return true;
}
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
}
相同main方法的结果
{Person@ba31=findingsea, Person@ba32=linyin, Person@ba33=findingsealy}
findingsea
linyin
findingsealy
可以看到,之前指出的亮点错误都得到了改正。那么,为什么会这样呢?
HashMap.put源码
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;
//先通过hashcode判断再进行equals判断
//由于“&&”具有短路功能,所以若hashcode相等,则后面的equals也不会调用
//HashMap中的key所对应的value就会被覆盖
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;
}
在HashMap
中,查找key
的比较顺序为:
- 计算对象的
Hash Code
,看在表中是否存在。 - 检查对应
Hash Code
位置中的对象和当前对象是否相等。
显然,第一步就是要用到hashCode()
方法,而第二步就是要用到equals()
方法。在没有进行重载时,在这两步会默认调用Object
类的这两个方法,而在Object
中,Hash Code
的计算方法是根据对象的地址进行计算的,那两个Person("003")
的对象地址是不同的,所以它们的Hash Code
也不同,自然HashMap
也不会把它们当成是同一个key
了。同时,在Object
默认的equals()
中,也是根据对象的地址进行比较,自然一个Person("003")
和另一个Person("003")
是不相等的。
理解了这一点,就很容易搞清楚为什么需要同时重写hashCode()
和equals
两个方法了。
- 重写()
是为了对同一个
key,能得到相同的
Hash Code,这样
HashMap就可以定位到我们指定的
key`上。 - 重写
equals()
是为了向HashMap
表明当前对象和key
上所保存的对象是相等的,这样我们才真正地获得了这个key
所对应的这个键值对。
还有一个细节,在Person
类中对于hashCode()
的重写方法为:
@Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
这里可能有疑惑的点在于:为什么可以用String
类型的变量的Hash Code
作为Person
类的Hash Code
值呢?这样new Person(new String("003"))
和new Person(new String("003"))
的Hash Code
是相等的吗?
来看看以下代码的输出:
System.out.println("findingsea".hashCode());
System.out.println("findingsea".hashCode());
System.out.println(new String("findingsea").hashCode());
System.out.println(new String("findingsea").hashCode());
728795174
728795174
728795174
728795174
可以看到四条语句的输出都是相等的,很直观的合理的猜测就是String
类型也重载了hashCode()
以根据字符串的内容来返回Hash Code
值,所以相同内容的字符串具有相同的Hash Code
。
同时,这也说明了一个问题:为什么在已知hashCode()
相等的情况下,还需要用equals()
进行比较呢?就是因为避免出现上述例子中的出现的情况,因为根据对Person
类的hashCode()
方法的重载实现,Person
类会直接用id
这个String
类型成员的Hash Code
值作为自己的Hash Code
值,但是很显然的,一个Person("003")
和一个String("003")
是不相等的,所以在hashCode()
相等的情况下,还需要用equals()
进行比较。
关于重写equals同时重写hashcode的更多相关文章
- 为什么重写equals还要重写hashcode??
equals和hashcode是object类下一个重要的方法,而object类是所有类的父类,所以所有的类都有这两个方法 equals和hashcode间的关系: 1.如果两个对象相同(即equal ...
- 【原创】关于java对象需要重写equals方法,hashcode方法,toString方法 ,compareto()方法的说明
在项目开发中,我们都有这样的经历,就是在新增表时,会相应的增加java类,在java类中都存在常见的几个方法,包括:equals(),hashcode(),toString() ,compareto( ...
- 为什么重写equals必须重写hashCode
目录 equals常见面试题 为什么要重写equals 重写equals不重写hashCode会存在什么问题 总结 equals常见面试题 在开始聊之前,我们先看几个常见的面试题,看看你能不能都回答上 ...
- 为什么重写equals必须重写hoshCode的基础分析
为什么重写equals必须重写hoshCode的基础分析 1.我们先来了解下原生的equals和hashCode代码 原生equals:它判断的是两个对象是否相等 原生hashCode值:它是根据内存 ...
- 为什么要重写equals()方法与hashCode()方法
在java中,所有的对象都是继承于Object类.Ojbect类中有两个方法equals.hashCode,这两个方法都是用来比较两个对象是否相等的. 在未重写equals方法我们是继承了object ...
- Hibernate中为什么要重写equals方法和hashcode方法
1.*为什么要重写equals方法,首先我们来看一下equals源码: public boolean equals(Object anObject) { if (this == anObject) { ...
- 为什么重写equals()必须重写hashCode()
主要原因是因为hashCode是用对象的内部地址转换成一个整数的,而equals比较得是两个对象,或者是两个对象的内容 如果你重写了equals,而保留hashCode的实现不变,那么很可能两个对象明 ...
- JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法
在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address { priva ...
- JAVA 重写equals和重写hashCode
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” 首先你需要了解: hashCode()的作用是获取哈希码(散列码) 它实 ...
- 为什么重写equals必须重写hashcode?
示例代码: class User { private String name; public User(String name) { this.name = name; } @Override pub ...
随机推荐
- redis 开启AOF 持久化
redis 开启AOF 找到redis 安装目录 打开 redis.conf 修改以下参数: appendonly yes (默认no,关闭)表示是否开启AOF持久化: append ...
- linux mount挂载命令
[root@localhost src]# mount 查询系统中已经挂载的设备 [root@localhost src]# mount -a 依据配置文件 /etc/fstab的内容,自动挂载
- 全网通4G工业路由器模块和串口转网口/4G/有线/WiFi/LTE模块的实现原理
随着现在信息化的高速发展,网络信息的需求量大增,在移动的4G流量的场合比如汽车上实现WiFi网络覆盖,户外wifi网络覆盖需求下,4G流量已经明显不够用,而网线到达的成本比较大,难以管控.在这市场痛点 ...
- Docker学习笔记之-部署.Net Core 3.1项目到Docker容器,并使用Nginx反向代理(CentOS7)(一)
上一节演示如何安装Docker,链接:Docker学习笔记之-在CentOS中安装Docker 本节演示 将.net core 3.1 部署到docker容器当中,并使用 Nginx反向代理,部署平台 ...
- vue组件使用name属性来生成递归组件
先来个简单的数据 1 lists = [{ 2 id: 1, 3 title: '第一层', 4 children: [{ 5 id: 3, 6 title: '第二层', 7 children: [ ...
- ui 自动化的测试用例从哪来
从手工测试当中选取,尽量选择 1.简单且需要反复回归 2.稳定且不会经常变化 3.优先覆盖核心功能
- C语言基础-C简介
C语言简介 C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的.C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现. 在 19 ...
- Java Arrays 和 List的相互转化
最近在 leetcode 刷题的时候遇到过好几次这样的情况:需要返回的数据类型是数组(Arrays),但是求解的时候并不知道数组的长度,这时候就需要先用 List 进行临时存储,最后再转化为 Arra ...
- 项目开发中的pro、pre、test、dev环境及SpringBoot多环境配置
一.介绍: pro:生产环境,面向外部用户的环境,连接上互联网即可访问的正式环境. pre:灰度环境,外部用户可以访问,但是服务器配置相对低,其它和生产一样. test:测试环境,外部用户无法访问,专 ...
- MyBatis重要核心概念
一.SqlSessionFactoryBuilder 从命名上可以看出,这个是一个 Builder 模式的,用于创建 SqlSessionFactory 的类.SqlSessionFactoryBui ...