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完全面向对象的一大特色.它 ...
随机推荐
- SQL 求中位值
题目A median is defined as a number separating the higher half of a data set from the lower half. Quer ...
- 如何调用openai的TTS模型
这是24年1月份写的了,调用代码大概率有变动,仅供参考. 1 什么是OpenAI的TTS模型 OpenAI的TTS模型是一种文本到语音(Text-to-Speech)模型,它可以将给定的文本转换为自然 ...
- 传染病模型 SI
参考了这篇写的很好的[1],讲了各种模型 因为是各种模型都是用微分方程写的,所以又去学习了一下微分方程 ,真的忘了有没有学过这个,反正一点印象也没有了. 好在[2] 这个文章又把我带回去了. SI 的 ...
- 计算QPS-Sentinel限流算法
sentinel 前方参考 计算QPS-Sentinel限流算法 https://www.cnblogs.com/yizhiamumu/p/16819497.html Sentinel 介绍与下载使用 ...
- Excel 国产化替换新方案
前言 在当前数字化转型和信创(信息技术应用创新)战略背景下,企业对于安全性.自主可控性和高效办公工具的需求日益增加.作为一款国产自主研发的高性能表格控件,SpreadJS 正成为替换 Excel 的最 ...
- SpringBoot系列:使用原生JDBC实现对数据库的增删改查
application.yml spring: datasource: username: root password: 123456 url: jdbc:mysql://localhost:3306 ...
- C++11新初始化方法 使用{}初始化变量
列表初始化 在C++11及以后的版本中,使用{}来初始化变量是一种新的初始化方法,称为列表初始化(List Initialization).这种初始化方法可以用来初始化内置类型.自定义类型以及聚合类型 ...
- Spring —— (Spring管理第三方资源)数据源对象管理
数据源对象管理 (用户名密码等敏感的数据不会直接放在bean中 而是放在properties文件中进行管理) 加载properties文件 在resource中创建 jdbc ...
- SaaS架构:流程架构分析
大家好,我是汤师爷~ 今天聊聊SaaS架构中的流程架构分析. 业务流程的概念 业务流程是企业为实现目标而制定的一套系统化的工作方法.它由一系列有序的业务活动组成,按照既定规则将资源(输入)转化为有价值 ...
- vue前端开发仿钉图系列(1)高德地图的使用详解
最近公司让参考钉图做图层模块相关的功能,很庆幸有机会细细研究地图相关的东西.因为手机端用的是高德地图,web端也使用高德地图.还是和往常一样,先贴上效果图. 步骤1.在高德开放平台注册信息,创建自己的 ...