java重写面试题_Java面试题:重写了equals方法,为什么还要重写hashCode方法?

核心问题:重写了equals方法,为什么还要重写hashCode方法?

这不仅仅是一道面试题,而且是关系到我们的代码是否健壮和正确的问题。在前面两篇文章涉及到了equals方法的底层讲解:《说说==和equals的区别?你的回答可能是错误的》和《Integer等号判断的内幕,你可能不知道?》。

本篇文章,带大家从底层来分析一下hashcode方法重写的意义以及如何实现。

回顾equals方法

我们先回顾一下Object的equals方法实现,并简单汇总一下使用equals方法的规律。

public boolean equals(Object obj) {

return (this == obj);

}

通过上面Object的源代码,可以得出一个结论:如果一个类未重写equals方法,那么本质上通过“==”和equals方法比较的效果是一样的,都是比较两个对象的的内存地址。

前面两篇文章讲到String和Integer在比较时的区别,关键点也是它们对equals方法的实现。

面试时总结一下就是:默认情况下,从Object类继承的equals方法与“==”完全等价,比较的都是对象的内存地址。但我们可以重写equals方法,使其按照需要进行比较,如String类重写了equals方法,比较的是字符的序列,而不再是内存地址。

与hashCode方法的关系

那么equals方法与hashCode方法又有什么关系呢?我们来看Object上equals方法的一段注释。

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

大致意思是:当重写equals方法后有必要将hashCode方法也重写,这样做才能保证不违背hashCode方法中“相同对象必须有相同哈希值”的约定。

此处只是提醒了我们重写hashCode方法的必要性,那其中提到的hashCode方法设计约定又是什么呢?相关的内容定义在hashCode方法的注解部分。

hashCode方法约定

关于hashCode方法的约定原文比较多,大家直接看源码即可看到,这里汇总一下,共三条:

(1)如果对象在使用equals方法中进行比较的参数没有修改,那么多次调用一个对象的hashCode()方法返回的哈希值应该是相同的。

(2)如果两个对象通过equals方法比较是相等的,那么要求这两个对象的hashCode方法返回的值也应该是相等的。

(3)如果两个对象通过equals方法比较是不同的,那么也不要求这两个对象的hashCode方法返回的值是不相同的。但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap等)能够提高性能。

其实,看到这里我们了解了hashCode的实现规约,但还是不清楚为什么实现equals方法需要重写hashCode方法。但我们可以得出一条规律:hashCode方法实际上必须要完成的一件事情就是,为equals方法认定为相同的对象返回相同的哈希值。

其实在上面规约中提到了哈希表,这也正是hashCode方法运用的场景之一,也是我们为什么要重写的核心。

hashCode应用场景

如果了解HashMap的数据结构,就会知道它用到“键对象”的哈希码,当我们调用put方法或者get方法对Map容器进行操作时,都是根据键对象的哈希码来计算存储位置的。如果我们对哈希码的获取没有相关保证,就可能会得不到预期的结果。

feb20c24e6e5ab212b21358e7264e3fb.png

而对象的哈希码的获取正是通过hashCode方法获取的。如果自定义的类中没有实现该方法,则会采用Object中的hashCode()方法。

在Object中该方法是一个本地方法,会返回一个int类型的哈希值。可以通过将对象的内部地址转换为整数来实现的,但是Java中没有强制要求通过该方式实现。

具体实现网络上有不同的说法,有说通过内置地址转换得来,也有说“OpenJDK8默认hashCode的计算方法是通过和当前线程有关的一个随机数+三个确定值,运用Marsaglia’s xorshift scheme随机数算法得到的一个随机数”获得。

无论默认实现是怎样的,大多数情况下都无法满足equals方法相同,同时hashCode结果也相同的条件。比如下面的示例重写与否差距很大。

public void test1() {

String s = "ok";

StringBuilder sb = new StringBuilder(s);

System.out.println(s.hashCode() + " " + sb.hashCode());

String t = new String("ok");

StringBuilder tb = new StringBuilder(s);

System.out.println(t.hashCode() + " " + tb.hashCode());

}

上面这段代码打印的结果为:

3548 1833638914

3548 1620303253

String实现了hashCode方法,而StringBuilder并没有实现,这就导致即使值是一样的,hashCode也不同。

上个示例中问题还不太明显,下面我们以HashMap为例,看看如果没有实现hashCode方法会导致什么严重的后果。

@Test

public void test2() {

String hello = "hello";

Map map1 = new HashMap<>();

String s1 = new String("key");

String s2 = new String("key");

map1.put(s1, hello);

System.out.println("s1.equals(s2):" + s1.equals(s2));

System.out.println("map1.get(s1):" + map1.get(s1));

System.out.println("map1.get(s2):" + map1.get(s2));

Map map2 = new HashMap<>();

Key k1 = new Key("A");

Key k2 = new Key("A");

map2.put(k1, hello);

System.out.println("k1.equals(k2):" + s1.equals(s2));

System.out.println("map2.get(k1):" + map2.get(k1));

System.out.println("map2.get(k2):" + map2.get(k2));

}

class Key {

private String k;

public Key(String key) {

this.k = key;

}

@Override

public boolean equals(Object obj) {

if (obj instanceof Key) {

Key key = (Key) obj;

return k.equals(key.k);

}

return false;

}

}

实例中定义了内部类Key,其中实现了equals方法,但未实现hashCode方法。存放于Map中的value值都是字符串“hello”。

代码分两段,第一段演示当Map的key通过实现了hashCode的String时是什么效果;第二段演示了当Map的key通过未实现hashCode方法的Key对象时是什么效果。

执行上述代码,打印结果如下:

s1.equals(s2):true

map1.get(s1):hello

map1.get(s2):hello

k1.equals(k2):true

map2.get(k1):hello

map2.get(k2):null

分析结果可以看出,对于String作为key的s1和s2来说,通过equals比较相等是自然的,获得的值也是相同的。但k1和k2通过equals比较是相等,但为什么在Map中获得的结果却不一样?本质上就是因为没有重写hashCode方法导致Map在存储和获取过程中调用hashCode方法获得的值不一致。

此时在Key类中添加hashCode方法:

@Override

public int hashCode(){

return k.hashCode();

}

再次执行,便可正常获得对应的值。

s1.equals(s2):true

map1.get(s1):hello

map1.get(s2):hello

k1.equals(k2):true

map2.get(k1):hello

map2.get(k2):hello

通过上面的典型实例演示了不重写hashCode方法的潜在后果。简单看一下HashMap中的put方法。

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

static final int hash(Object key) {

int h;

return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,

boolean evict) {

Node[] tab; Node p; int n, i;

if ((tab = table) == null || (n = tab.length) == 0)

n = (tab = resize()).length;

// 通过哈希值来查找底层数组位于该位置的元素p,如果p不为null,则使用新的键值对来覆盖旧的键值对

if ((p = tab[i = (n - 1) & hash]) == null)

tab[i] = newNode(hash, key, value, null);

else {

Node e; K k;

// (二者哈希值相等)且(二者地址值相等或调用equals认定相等)。

if (p.hash == hash &&

((k = p.key) == key || (key != null && key.equals(k))))

e = p;

else if (p instanceof TreeNode)

e = ((TreeNode)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;

}

}

// 如果底层数组中存在传入的Key,那么使用新传入的覆盖掉查到的

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;

}

在上述方法中,put方法在拿到key的第一步就对key对象调用了hashCode方法。暂且不看后面的代码,如果没有重写hashCode方法,就无法确保key的hash值一致,后续操作就是两个key的操作了。

重写hashCode方法

了解了重写hashCode方法的重要性,也了解了对应的规约,那么下面我们就聊聊如何优雅的重写hashCode方法。

首先,如果使用IDEA的话,那么直接使用快捷键即可。

f13b0a19dbb095f1a7a2cec83e9c5c6e.png

生成的效果如下:

@Override

public boolean equals(Object o) {

if (this == o) {

return true;

}

if (o == null || getClass() != o.getClass()) {

return false;

}

Key key = (Key) o;

return Objects.equals(k, key.k);

}

@Override

public int hashCode() {

return Objects.hash(k);

}

根据需要可对生成的方法内部实现进行修改。在上面的实例中用到了java.util.Objects类,它的hash方法的优点是如果参数为null,就只返回0,否则返回对象参数调用的hashCode的结果。Objects.hash方法源码如下:

public static int hash(Object... values) {

return Arrays.hashCode(values);

}

其中Arrays.hashCode方法源码如下:

public static int hashCode(Object a[]) {

if (a == null)

return 0;

int result = 1;

for (Object element : a)

result = 31 * result + (element == null ? 0 : element.hashCode());

return result;

}

当然此处只有一个参数,也可以直接使用Objects类hashCode方法:

public static int hashCode(Object o) {

return o != null ? o.hashCode() : 0;

}

如果是多个属性都参与hash值的情况建议可使用第一个方法。只不过需要注意,在类结构(成员变量)变动时,同步增减方法里面的参数值。

小结

当我们准备面试时,一直在背诵“实现equals方法的同时也要实现hashCode方法”,牢记这些结论并没有错。但我们也不能因为匆忙准备面试题,而忘记了这些面试题之所以频繁出现的原因是什么。当深入探索之后,会发现在那些枯燥的结论背后还有这么多不容忽视的知识点,还有这么多有意思的设计与陷阱。

我是觉得越研究越有意思,越研究越发现自己曾经的无知。你呢?关注一下,期待下一篇文章吧。

希望与广大网友互动??

点此进行留言吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/551723.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电脑显示器闪屏_Win7系统电脑显示器屏幕闪屏的解决办法

Win7系统电脑闪屏怎么办&#xff1f;近日一个用户反馈&#xff0c;在使用Win7系统电脑的时候&#xff0c;会出现闪屏的问题&#xff0c;该如何解决呢&#xff1f;请看下文具体解决办法。解决办法&#xff1a;1、首先右击桌面空白处&#xff0c;并在右键菜单中&#xff0c;直接选…

win10更新不动_win10沙盒功能和其他同类程序的区别

刚刚在使用WIN10自带的沙盒功能&#xff0c;发现吃瘪了。具体吃瘪过程如下&#xff1a;WIN10沙盒https://www.zhihu.com/video/1145279384016584704明白人看懂了是啥意思&#xff0c;后面的废话可以跳过。总结一句话&#xff0c;WIN10沙盒在我文章发布日期前&#xff0c;软件环…

查看grafana版本_使用 Prometheus 与 Grafana 为 Kubernetes 集群建立监控与警报机制

作者 | Gregoire DAYET 策划 | 田晓旭 IT 团队已经明确意识到对基础设施进行监控的必要性。目前市面上存在着大量适用于传统基础设施且历史悠久的解决方案&#xff1a;Nagios、Zabbix 等工具都是其中的代表。但这些工具并不足以解决 Kubernetes 生态系统在多层级抽象与故障排查…

java的注解_java-注解相关

注解概念&#xff1a;说明程序的&#xff0c;给计算机看注释&#xff1a;用文字描述程序先了解一些怎么正常javadoc文档1&#xff1a;给类或者方法添加doc注释2&#xff1a;通过命令javadoc 执行 类.java文件新建的类&#xff1a;/*** 注解doc演示*authorquan*sincejdk 1.7*ver…

linux删除文件_Linux删除文件夹用什么命令

请关注本头条号&#xff0c;每天坚持更新原创干货技术文章。如需学习视频&#xff0c;请在微信搜索公众号“智传网优”直接开始自助视频学习1. 前言本文主要讲解Linux删除文件夹或目录的命令rmdir和rm的使用方法。Windows上的文件夹与Linux系统里的目录是同一概念。默认情况下&…

shrio 登陆后 还是失效_在 iPhone 上取消订阅后,应用或内容会立即失效吗?

在 iPhone 中&#xff0c;一些应用和服务需要进行订阅&#xff0c;即您需要支付相应的费用以获得应用或服务中内容的访问权限。如果您想要取消订阅某个项目&#xff0c;可以按以下步骤操作&#xff1a;前往 iPhone “设置”-“Apple ID”-“iTunes Store 与 App Store”&#x…

wifi 信道_WiFi网速太慢,四招就可以让无线网络变得顺畅

和WIFI网速相关联的因素主要有四个&#xff0c;对应解决方法也就有四个。频段冲突是WIFI网速变慢很常见的问题。现实中常用的WIFI频段有2.4GHZ和5GHZ两个大频段&#xff0c;也好比两条高速公路。中国2.4GHZ频段里有11个信道&#xff0c;5GHZ有15个信道。多少个信道就好比有多少…

判读一个对象不为空_“人不为己,天诛地灭”的真实含义

“人不为己&#xff0c;天诛地灭”出自《佛说十善业道经》&#xff0c;其意思不是“一个人如果不为自己谋利益&#xff0c;就会遭到天地诛灭”&#xff0c;而是“一个人如果不修行自己的德行&#xff0c;那么就会为天地所不容”。“为”是修习、修炼、修行的意思&#xff0c;修…

c++opencv显示中文_OpenCV如何入门秘籍

OpenCV简介谈起入门&#xff0c;我们首先要搞明白OpenCV是什么&#xff1f;OpenCV的全称是Open Source Computer Vision Library&#xff0c;是一种计算机视觉库&#xff0c;主要用于处理摄像头采集的图像。既然说到了是一种库&#xff0c;就要聊聊这个库使用什么语言编写的。O…

安卓开发文档_鸿蒙2.0,HarmonyOS开发体验!

“没有人能够熄灭漫天星光”。在9月10日的华为2020开发者大会上&#xff0c;余承东掷地有声地说道。从去年开放的鸿蒙1.0&#xff0c;到今年的2.0。仅仅一年时间&#xff0c;华为就把基础设施全部搭建好。从之前的感知不强&#xff0c;到现在的触手可得&#xff0c;让果核这个半…

python字符串类型_Python3的字符串类型(疯狂Python)

先看一下本篇文章要讲的内容目录&#xff1a; 4.2 字符串入门String4.2.1 repr和字符串4.2.2 input和raw_input4.2.3 长字符串4.2.4 bytes4.2.5 字符串格式化4.2.6 Python自带两个帮助函数4.2.7 删除多余空白4.2.8 字符串的查找&#xff0c;替换4.2.9 字符串的分割&#xff0c;…

盘点苹果微信聊天记录恢复的3大常用方法!

微信聊天记录一旦被误删除或者意外丢失&#xff0c;那确实是一件麻烦的事情。如果只是丢失了文件、图片、视频等&#xff0c;那么重新让好友转发就行。 那如果是想恢复全部聊天记录呢&#xff1f;苹果微信聊天记录恢复有哪些方法&#xff1f;如果你还不知道正确的恢复方法&…

python采用面向对象编程模式吗_如何理解 Python 中的面向对象编程?

现如今面向对象编程的使用非常广泛&#xff0c;本文我们就来探讨一下Python中的面向对象编程。作者 | Radek Fabisiak 译者 | 弯月&#xff0c;责编 | 郭芮 以下为译文&#xff1a; Python支持多种类型的编程范式&#xff0c;例如过程式编程、函数式编程、面向对象编程&#xf…

android 打开系统相册_这5款常用Android手机自动化测试工具你要收藏

1、Monkey是Android SDK自带的测试工具&#xff0c;在测试过程中会向系统发送伪随机的用户事件流&#xff0c;如按键输入、触摸屏输入、手势输入等)&#xff0c;实现对正在开发的应用程序进行压力测试&#xff0c;也有日志输出。实际上该工具只能做程序做一些压力测试&#xff…

php 替换某个字符,php中如何替换字符串中的某个字符-PHP问题

正在PHP中&#xff0c;能够应用strtr()函数完成字符串交换。起首咱们简略理解下strtr()函数的界说及语法。语法&#xff1a;string strtr( string $str, string $from, string $to)第一个参数示意待转换的字符串。第二个参数示意字符串中与将要被转换的目的字符 to 绝对应的源字…

python提示对话框自动关闭_Python实现定时自动关闭的tkinter窗口方法

Python实现定时自动关闭的tkinter窗口方法 更新时间&#xff1a;2019年02月16日 09:13:27 作者&#xff1a;Python_小屋 今天小编就为大家分享一篇Python实现定时自动关闭的tkinter窗口方法&#xff0c;具有很好的参考价值&#xff0c;希望对大家有所帮助。一起跟随小编过来看看…

x5内核有什么优点_接上U盘就是NAS私有云,蒲公英X5入手测评

接上U盘就是NAS私有云&#xff0c;蒲公英X5入手测评&#xff01;现在很多人喜欢在家里配置一台NAS&#xff0c;这样远程访问家里的数据不仅方便&#xff0c;而且可以即时备份PC以及手机等设备的数据。一旦手机丢失或电脑数据损坏&#xff0c;还可以通过NAS来恢复数据。但是对于…

小程序如何调用php程序,微信小程序调用PHP后台接口 解析纯html文本

搜索热词1、微信js动态传参&#xff1a;PHP/Home/Xiaoxxf/activity_detail?a_idoptions.id,//含富文本htmldata: {is_detail:1},method: GET,// OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECTheader: {Content-Type: application/json},success: function (res) {that.setD…

手机联系人头像包_一组抖音上很火的表情包,这里都有,一起来可可爱爱吧

我是最新抖音表情包马上到表情包轿车带火花|马上到表情包白色汽车动图em..不知道为什么这个表情包也很火&#xff0c;感jio没什么特别&#xff0c;这个就厉害了&#xff0c;你以为我是雨伞&#xff1f;那你就错了。砰~可可爱爱&#xff01;太萌了。像不像考试中的你&#xff1f…

python视频处理代码_python如何实现视频转代码视频

本文实例为大家分享了python如何实现视频转代码视频的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下 # -*- coding:utf-8 -*- #coding:utf-8 import argparse import os import cv2 import subprocess from cv2 import VideoWriter, VideoWriter_fourcc, imread, re…