浅谈为何要重写 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方法的定义为:

  1. public boolean equals(Object obj) {
  2. return (this == obj);
  3. }
  4. 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()

  1. transient Entry[] table;
  2. public V put(K key, V value) {
  3. if (key == null)
  4. return putForNullKey(value);        //HashMap支持null的key
  5. int hash = hash(key.hashCode());        //根据key的hashCode计算Hash值
  6. int i = indexFor(hash, table.length);   //搜索指定Hash值在对应table中的索引
  7. for (Entry<K,V> e = table[i]; e != null; e = e.next) {    //在i索引处Entry不为空,循环遍历e的下一个元素
  8. Object k;
  9. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  10. V oldValue = e.value;
  11. e.value = value;
  12. e.recordAccess(this);
  13. return oldValue;
  14. }
  15. }
  16. //若i索引处Entry为null,表明此处还没有Entry
  17. modCount++;
  18. addEntry(hash, key, value, i);  //将key、value添加到i索引处
  19. return null;
  20. }
  21. static int hash(int h) {
  22. h ^= (h >>> 20) ^ (h >>> 12);
  23. return h ^ (h >>> 7) ^ (h >>> 4);
  24. }

对于任意给定的对象,只有它的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为何要同时重写的更多相关文章

  1. hashCode() 和 equals()的问题解答及重写示范

    本章的内容主要解决下面几个问题: 1 equals() 的作用是什么? 2 equals() 与 == 的区别是什么? 3 hashCode() 的作用是什么? 4 hashCode() 和 equa ...

  2. java的hashCode和equals为什么要同时重写?

    原因: java规范:相等的对象必须具有相等的散列码(hashCode) 同时对于HashSet和HashMap这些基于散列值(hash)实现的类.key的判断是通过hashCode完成,且散列也是通 ...

  3. hashCode()和equals()的用法

    使用hashCode()和equals() hashCode()和equals()定义在Object类中,这个类是所有java类的基类,所以所有的java类都继承这两个方法. hashCode()方法 ...

  4. 使用hashCode()和equals()方法 - Java

    在这篇文章中,我将指出我对hashCode()和equals()方法的理解.我将讨论它们的默认实现以及如何正确地覆盖它们.我还将使用Apache Commons包中的实用工具类来实现这些方法. has ...

  5. hashCode 与 equals

    面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?”   hashCode()介绍 hashCode() 的作用是获取哈希码, ...

  6. hashCode和equals的区别

    关注公众号,大家可以在公众号后台回复“博客园”,免费获得作者 Java 知识体系/面试必看资料. 有面试官会问:你重写过 hashcode 和 equals 么,为什么重写equals时必须重写has ...

  7. 关于hashcode和equals方法说明

    一.前言 我们都知道,要比较两个对象是否相等时需要调用对象的equals()方法,即判断对象引用所指向的对象地址是否相等,对象地址相等时,那么与对象相关的对象句柄.对象头.对象实例数据.对象类型数据等 ...

  8. 用HashSet的add方法谈hashcode和equals方法重写

    本文主要通过用HashSet的add方法讲一下hashCode和equals方法重写.错误的地方望指正. 1.了解HashSet的add方法 了解一个方法的好办法是看源码,所以先看源码 private ...

  9. 为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。

    我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...

  10. 【转】 如何重写hashCode()和equals()方法

    转自:http://blog.csdn.net/neosmith/article/details/17068365 hashCode()和equals()方法可以说是Java完全面向对象的一大特色.它 ...

随机推荐

  1. Windows平台体验StableSwarmUI-0.6.4-Beta经验版

    目录 StableSwarmUI install 经验版 StableSwarmUI 配置后端 StableSwarmUI 快捷安装脚本 StableSwarmUI 安装与启动 sd_xl_base_ ...

  2. 【转】一种Vue应用程序错误/异常处理机制

    在前端应用程序中,最常见的错误/异常类型可能包括以下几种: 语法错误:使用了一些错误的语法 运行时错误:由于执行期间的非法操作导致的 逻辑错误:由于程序逻辑错误 Http 错误:API 返回的错误 有 ...

  3. QT框架实现自定义形状截图效果

    文章目录 QT框架普通截图运行效果 QT框架系统级热键的原理 注册热键 反注册热键 获取系统级唯一的整数ID 删除系统级唯一整数ID 原生事件过滤器 QT框架截图的原理 截图窗口 拉框操作 系统剪切板 ...

  4. Flutter Engage China 开发者常见问题解答 | 下篇

    再次感谢大家对 Flutter Engage China 活动 的关注和积极参与!我们在活动前后收到了很多来自开发者的反馈和问题,Flutter 团队和演讲嘉宾在直播 Q&A 环节中也针对部分 ...

  5. MyBatis动态增改,多字段模糊查询

    示例: insert insert into bargain_products <trim prefix="(" suffix=")" suffixOve ...

  6. PHP面试,ES

    什么是Elasticsearch? Elasticsearch是一个开源的分布式搜索和分析引擎,用于存储.搜索和分析大量数据.它基于Lucene搜索引擎构建,可以快速地执行全文搜索.结构化查询.分析和 ...

  7. 墨天轮最受DBA欢迎的数据库技术文档-故障处理案例篇

    在之前发布的<墨天轮最受欢迎的技术文档-容灾备份篇>中,大家说想看故障处理案例篇的内容,这不!编辑部快马加鞭给大家整理来了,希望能够帮助到大家. 数据库故障可能出现在内存.网络.CPU.硬 ...

  8. Android复习(一)基础知识

    1. 现在可以使用 Kotlin.Java 和 C++ 语言编写 Android 应用 2.Android四大组件依然坚挺,这是基础并且没有改变的迹象 Activity 服务 广播接收器 内容提供程序 ...

  9. KubeSphere 社区双周报 | FluentBit 新增 tcp 输入插件 | 2023.09.29-10.12

    KubeSphere 社区双周报主要整理展示新增的贡献者名单和证书.新增的讲师证书以及两周内提交过 commit 的贡献者,并对近期重要的 PR 进行解析,同时还包含了线上/线下活动和布道推广等一系列 ...

  10. 《使用Gin框架构建分布式应用》阅读笔记:p77-p87

    <用Gin框架构建分布式应用>学习第5天,p77-p87总结,总计11页. 一.技术总结 1.Go知识点 (1)context 2.on-premises software p80, A ...