hashcode和equals为何要同时重写
浅谈为何要重写 hashcode()与equals()
首先,这两个方法都来自于Object对象,根据API文档查看下原意。(1)public boolean equals(Objectobj),对于任何非空引用值 x 和 y,当且仅当 x 和 y 引用同一个对象时,此方法才返回 true;注意:当此方法被重写时,通常有必要重写
hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。(2)public int hashCode() 返回该对象的哈希码值。支持该方法是为哈希表提供一些优点,例如,java.util.Hashtable 提供的哈希表。
我们知道,如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。特别指出,此时,利用equals比较八大包装对象(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它对象都是比较的引用地址。那产生了一个问题,为什么jdk中希望我们在重写equals时,非常有必要重写hashcode呢?
我的理解是hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致,如在存储散列集合时(如Set类),将会存储了两个值一样的对象,导致混淆,因此,就也需要重写hashcode。为了保证这种一致性,必须满足以下两个条件:
(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true
(2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false
下面,通过一个简单的例子来验证一下。
import java.util.*;
class BeanA {
private int i;
public BeanA(int i) {
this.i = i;
}
public String toString() {
return " " + i;
}
public boolean equals(Object o) {
BeanA a = (BeanA) o;
return (a.i == i) ? true : false;
}
public int hashCode() {
return i;
}
}
public class HashCodeTest {
public static void main(String[] args) {
HashSet<BeanA> set = new HashSet<BeanA>();
for (int i = 0; i <= 3; i++){
set.add(new BeanA(i));
}
System.out.println(set);
set.add(new BeanA(1));
System.out.println(set.toString());
System.out.println(set.contains(new BeanA(0)));
System.out.println(set.add(new BeanA(1)));
System.out.println(set.add(new BeanA(4)));
System.out.println(set);
}
}
我们在类BeanA中重写了equals和hashcode方法,这样在存储到HashSet数据集中,将保证不会出现重复的数据;如果把这两个方法去掉后,那些重复的数据仍会存入HashSet中,这就与HashSet强调的元素唯一性相违背,大家可以把这两个方法注释掉再运行一下。
因此,我们就可以理解在一些java类中什么情况下需要重写equals和hashcode。比如:在hibernate的实体类中,往往通过一个主键(或唯一标识符)来判断数据库的某一行,这就需要重写这两个方法。因为,Hibernate保证,仅在特定会话范围内,持久化标识(数据库的行)和Java标识是等价的。因此,一旦 我们混合了从不同会话中获取的实例,如果希望Set有明确的语义,就必 须实现equals() 和hashCode()。
如果你的对象想散列存储的集合中或者想作为散列Map的Key时(HashSet、HashMap、Hashtable等)那么你必须重写equals()方法,这样才能保证唯一性。在重写equals()方法的同时,必须重写hashCode()方法?当然,在这种情况下,你不想重写hashCode()方法,也没有错,但是sun建议这么做,重写hashCode只是技术要求(为了提高效率)。
当在散列集合中放入key时,将自动查看key对象的hashCode值,若此时放置的hashCode值和原来已有的hashCode值相等,则自动调用equals()方法,若此时返回的为true则表示该key为相同的key值,只会存在一份。
Object中关于hashCode和equals方法的定义为:
- public boolean equals(Object obj) {
- return (this == obj);
- }
- public native int hashCode();
基类的hashCode是一个native方法,访问操作系统底层,它得到的值是与这个对象在内存中的地址有关。
Object的不同子类对于equals和hashCode方法有其自身的实现方式,如Integer和String等。
equals相等的,hashCode必须相等
hashCode不等的,则 equals也必定不等。
hashCode相等的 equals不一定相等(但最好少出现 hashCode相等的情况)。
HashMap的put(K, Value)方法提供了一个根据K的hashCode来计算Hash码的方法hash()
- transient Entry[] table;
- public V put(K key, V value) {
- if (key == null)
- return putForNullKey(value); //HashMap支持null的key
- int hash = hash(key.hashCode()); //根据key的hashCode计算Hash值
- int i = indexFor(hash, table.length); //搜索指定Hash值在对应table中的索引
- for (Entry<K,V> e = table[i]; e != null; e = e.next) { //在i索引处Entry不为空,循环遍历e的下一个元素
- Object k;
- if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
- V oldValue = e.value;
- e.value = value;
- e.recordAccess(this);
- return oldValue;
- }
- }
- //若i索引处Entry为null,表明此处还没有Entry
- modCount++;
- addEntry(hash, key, value, i); //将key、value添加到i索引处
- return null;
- }
- static int hash(int h) {
- h ^= (h >>> 20) ^ (h >>> 12);
- return h ^ (h >>> 7) ^ (h >>> 4);
- }
对于任意给定的对象,只有它的hashCode()返回值相同,那么程序调用hash(int h)方法所计算得到的Hash码值总是相同的。接下来会调用indexFor(int h, int length)方法来计算该对象应该保存在table数组的哪个索引处。
它总是通过h & (table.length - 1)来得到该对象的保存位置--而HashMap底层数组的长度总是2的n次方,这样就保证了得到的索引值总是位于table数组的索引之内。
当通过key-value放入HashMap时,程序就根据key的hashCode()来觉得Entry的存储位置:若两个Entry的key的hashCode()相同那么他们的存储位置相同;若两个Entry的key的equals()方法返回true则新添加Entry的value将覆盖原有Entry的value,但key不会覆盖;若两个Entry的key的equals()方法返回false则新加的Entry与集合中原有的Entry形成Entry链。
HashSet的add(E)的实现是通过HashMap的put方法来实现的。(HashSet内部是通过HashMap来实现的,TreeSet则是通过TreeMap来实现的)
根据key的hashCode计算器Hash值,然后取得该Hash值在表table的索引,取得该索引i对应的table中的Entry,判断key的equals()。
hashcode和equals为何要同时重写的更多相关文章
- hashCode() 和 equals()的问题解答及重写示范
本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equa ...
- java的hashCode和equals为什么要同时重写?
原因: java规范:相等的对象必须具有相等的散列码(hashCode) 同时对于HashSet和HashMap这些基于散列值(hash)实现的类.key的判断是通过hashCode完成,且散列也是通 ...
- hashCode()和equals()的用法
使用hashCode()和equals() hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法. hashCode()方法 ...
- 使用hashCode()和equals()方法 - Java
在这篇文章中,我将指出我对hashCode()和equals()方法的理解.我将讨论它们的默认实现以及如何正确地覆盖它们.我还将使用Apache Commons包中的实用工具类来实现这些方法. has ...
- hashCode 与 equals
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” hashCode()介绍 hashCode() 的作用是获取哈希码, ...
- hashCode和equals的区别
关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 有面试官会问:你重写过 hashcode 和 equals 么,为什么重写equals时必须重写has ...
- 关于hashcode和equals方法说明
一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...
- 用HashSet的add方法谈hashcode和equals方法重写
本文主要通过用HashSet的add方法讲一下hashCode和equals方法重写.错误的地方望指正. 1.了解HashSet的add方法 了解一个方法的好办法是看源码,所以先看源码 private ...
- 为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。
我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...
- 【转】 如何重写hashCode()和equals()方法
转自:http://blog.csdn.net/neosmith/article/details/17068365 hashCode()和equals()方法可以说是Java完全面向对象的一大特色.它 ...
随机推荐
- UDT(一):概览
1. 参考链接 官网 https://udt.sourceforge.io/ 谷博士对UDT的简单介绍 https://udt.sourceforge.io/doc/udt-2009.ppt 获取UD ...
- Openstack-Train( 一)基础环境
openStack-train 搭建部署 当面对KVM集群的时候,我们对KVM的管理以及宿主机的管理就会遇到很大的难度,例如: 查看每一个宿主机有多少台KVM虚拟机? 查看每一个宿主机资源信息,每一个 ...
- Storybook version8 智能化构建组件文档与单元测试
根据官方文档说法,storybook 是一个独立构建前端UI组件与页面的车间. Storybook is a frontend workshop for building UI components ...
- 【SpringBoot Demo】MySQL + JPA + Hibernate + Springboot + Maven Demo
主要包含:springboot+jpa+hibernate+mysql+lombok (两年前写过一个,现在重新记录一个) 1. 目录结构: 2. pom 文件 1 <?xml version= ...
- CSS – Counters
介绍 counter 有点像 JS 的 for loop index. 最常用到的地方就是做 ol > li. 参考: W3Schools – CSS Counters 默认 ol > l ...
- OData – OData vs GraphQL
GraphQL 很火, 很厉害, 但是它和 OData 有本质的区别. 所以并不是说任何一样对比另一个绝对的好. GraphQL is not OData twitter 的讨论 有几个点是我能 Ge ...
- 《MySQL 5.7从入门到精通(视频教学版)》代码课件教学视频下载
<MySQL 5.7从入门到精通(视频教学版)>代码课件教学视频下载 https://pan.baidu.com/s/1ZufDV6a_PEnjp-Bdh4IkuQ 提取码:vgnr 无版 ...
- .Net 依赖注入深入探索,做一个DI拓展,实现一个简易灵活的 自动依赖注入框架
一.依赖注入相关知识 1.1.依赖注入的原理和优点 依赖注入(DI),是IOC控制反转思想 的实现.由一个DI容器,去统一管理所有的服务生命周期,服务的创建.销毁.获取,都是由DI容器去处理的. 依赖 ...
- 2021年9月国产数据库排行榜-墨天轮:达梦奋起直追紧逼OceanBase,openGauss反超PolarDB再升一位
2021年9月国产数据库排行榜已在墨天轮发布,本月参与排名的数据库总数达到了142个. 一.9月国产数据库流行度排行榜前15名 先来看看排行榜前五名,虽然PingCAP的TiDB分数本月下降31.82 ...
- 0503-autograd实战之线性回归
0503-autograd实战之线性回归 目录 一.用 variable 实现线性回归(autograd 实战) 二.第五章总结 pytorch完整教程目录:https://www.cnblogs.c ...