equals和==和hashcode的恩怨情仇
一、前言:
equals和==和hashcode是java中的基础中的基础,但是确实容易被问到,而且因为工作中经常用所以很容易在复习时遗漏掉,这几天面了个大厂就在这个问题上翻车了,不仅记混了,而且很坚信自己记混的答案,好在其他问题答的不错,加上面试官容忍度高放过一马,所以今天也就详细整理了下它们三个的恩怨情仇
二、【==】双等号
【==】其实没那么复杂,它的功能就只是比较两边的值是否相等。只是如果变量是引用类型(Integer、String、Object)的话,比较的就是内存地址,因为引用类型变量存的值就是对象的地址而已。而基本类型(int、double)的变量存的就是它们的值,和内存地址什么的无关。
所以我们在用【==】比较引用类型的变量时,就麻烦了。如果引用类型是Integer、String、Long这种,我们比较它的时候肯定是打算比较它们代表的值,而不是比较什么内存地址。
三、equals方法:
equals()是Object类的方法,而Object类又是所有类的祖宗(父类),所以所有的引用类型都有equals()方法。但是有很多类都会重新equals以及hashcode方法
1.object类的equals
先看下object的equals方法
public boolean equals(Object obj) {return (this == obj);}
object类的equals方法很简单其实就是【==】
2.Integer类的equals
可以看出Integer类的equals方法其实时比较的值
public boolean equals(Object obj) {if (obj instanceof Integer) {return value == ((Integer)obj).intValue();}return false;}
3.String类的equals方法
public boolean equals(Object anObject) {//判断引用if (this == anObject) {return true;}//判断是否是Stringif (anObject instanceof String) {String anotherString = (String)anObject;int n = value.length;//判断字符长度if (n == anotherString.value.length) {char v1[] = value;char v2[] = anotherString.value;int i = 0;//判断每一个字符是否相等while (n-- != 0) {if (v1[i] != v2[i])return false;i++;}return true;}}return false;}
大概流程是:
(1)判断引用是否相等
(2)判断是否是String
(3)判断字符长度是否相等
(4)遍历判断每个字符是否相等
四、==和equals的区别
如果是基本类型,只能用【==】,它们没有equals方法。
如果是引用类型,直接【==】的话是比较内存地址。如果这个引用类型重写过equals()方法,可以用equals()方法比较内容,如Integer、String……等常用的引用类型都是有重写过euqals()的。
五、int、Integer和new Integer()多种赋值比较区别
1.int
int无需多说,基本类型,无论是声明变量还是==比较我们都很清楚了,都只是比较值而已。
2.Integer
Integer的初始化就不太一样了
Integer i = 10;
Integer j = 10;
System.out.println(i==j);
如果这么进行赋值,那么返回是true,按照之前的说法比较的是地址那么必然是不一样的才对啊,这里就涉及了java自动装箱
系统会自动将赋给Integer变量的int值封装成一个Integer对象,例如Integer a1=3
,实际上的操作是
Integer a = Integer.valueof(3)
但是这个自动装箱也是有范围限制的,当int的范围在-128——127之间时,会通过一个IntegerCache缓存来创建Integer对象;当int不在该范围时,就直接是new Integer()创建对象
3.new Integer()
这种赋值就不用多说了,等号会比较引用,所以不会相等
4.其他包装类
例如Long和Integer是一样的,有一个-128~127的缓存,Double和Float则是没有缓存直接返回new。
六、String直接赋值和new String()比较的区别
String也需要特别的说明一下,因为它并不属于基本类型,所以没有int、long那种类型,这种情况我们只需要比较两种情况,直接赋值和new,也就是比较:
//第一组
String a=new String("test");
String b="test";
System.out.println(a==b);//第二组
System.out.println("a"=="a");//第三组String a=new String("test");
String b=new String("test");
System.out.println(a ==b);
答案是 false,true,false,因为只要有一边是new的方式初始化的变量,那地址肯定是不一样的,并且这里也是用【==】进行比较地址,自然是false。
字符串常量池
关于String的直接赋值,则需要先说明一下字符串常量池。
String类是我们平时项目中使用的很多的对象类型,jvm为了提升性能和减少内存开销,以及避免字符的重复创建,专门维护了一块字符串常量池的内存空间。当我们需要使用字符串时,就会先去这个常量池中找,如果存在该字符串,就直接将该字符串的地址返回去直接用。如果不存在,则用new进行初始化并将这个字符串投入常量池中以供后面使用。
这个字符串常量池就是通过直接赋值时使用的,所以如果是直接赋值的方式初始化相同内容的String,那么其实都是从同一个常量池里取到字符串,地址指向的是同一个对象,自然结果都是相同的。
字符串的拼接比较
String a = "hello";
String d = "helloworld";
final String c = "hello";
System.out.println(d == a + "world"); //false
System.out.println(d == "hello" + "world"); //true
System.out.println(d == c + "world"); //false
如果只看内容,d都是和helloworld进行了比较,但是带有变量的就是false,纯字符串的就是true,这是为什么呢?
其实这跟jvm的编译期有关,在进行编译时,它可以识别出"hello" + “world"这样的字面量和"helloworld"是相同的,认为这是直接的字面量赋值。通过反编译其实可以看出来,编译后,它直接将"hello” + “world"编译成了"helloworld”。所以自然都是在同一个常量池里找,比较起来也是相同的。
但是加上final就有不一样了,这里由于c被视为了常量,所以同样认为是字面量赋值,最终还是在常量池中获取的值,结果就是true了。
如果有任一边是通过new赋值的,那么结果肯定是false。
如果两边都是直接赋值的,或是通过final变量进行拼接赋值的,结果是true。只要有一边有涉及非final变量,结果就是false。
七、 hashCode()
1.简介:
hashCode()方法的作用是获取哈希码,也称为散列码,它实际上只是返回一个int整数。
但是它主要是应用在散列表中,如:HashMap,Hashtable,HashSet,在其他情况下一般没啥用,原因后面会说明。
Java中的hashCode并没有真正的实现为每个对象生成一个唯一的hashCode,还是会有一定的重复几率。
object中的hashcode()
public native int hashCode();
2.重写hashCode()
核心是保证相同的对象能返回相同的hash值,尽量做到不同的对象返回不同的hash值。主要能保证核心的规则即可。例如Integer的hashcode就很简单粗暴,直接返回它所代表的的value值。也就是1的hashcode还是1,100的hashcode还是100。
public int hashCode() {return Integer.hashCode(value);}
String 的hashCode就相对复杂一些了通过这个公式计算的hashcode
|
public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}
在java的很多类中都会重写equals和hashCode方法,这是为什么呢?比如我定义两个字符相同的字符串,那么对它们进行比较时,我想要的结果应该是相等的,如果你不重写equals和hashCode方法,他们肯定是不会相等的,因为两个对象的内存地址不一样。
3.hashCode()和equals() 的关系
和equal()方法作用类似,hashCode()在java中的也是用于比较两个对象是否相等。我们应该都大概听过一句话:重写equals()方法的话一定要重写hashCode()。这里就说明一下这点。
分两种情况:
(1)首先一种情况是确定了不会创建散列表的类,我们不会创建这个类的HashSet、HashMap集合之类的。这种情况下,这个类的hashCode()和equals() 完全没有任何关系,当我们比较这个类的两个对象时,直接用的就是equals(),完全用不上hashCode()。自然,也没啥必要重写。
(2)另一种情况自然就是可能会需要使用到散列表的类了,这里hashCode()和equals()就比较有关系了:
在散列表的使用中,经常需要大量且快速的对比,例如你每次插入新的键值对,它都必须和前面所有的键值对进行比较,确保你没有插入重复的键。这样如果每个都用equals,可想而知,性能是十分可怕的。如果你的equals再复杂一些,那就凉凉了。
这时候就需要hashCode()了,如果利用hashCode()进行对比,只要生成一个hash值比较数字是否相同就可以了,能显著的提高效率。但是虽然如此,原本的equals()方法还是需要的,hashCode()虽然效率高,可靠性却不足,有时候不同的键值对生成的hashcode也是一样的,这就是哈希冲突。
在使用时,hashCode()将与equals结合使用。虽然hashcode可能会让不同的键产生相同的hashcode,但是它能确保相同的对象返回的hashcode一定是相同的(除非你重写没写好),我们只需要利用后面这点,一样可以提高效率。
在散列表中进行对比时,先比较hashCode(),如果它不相等,那说明两个对象肯定不可能相同,就可以直接进行下一个比较了。但如果hashCode()相同,因为哈希冲突的缘故我们无法直接判断两个对象是相同的,就必须继续比较equals()来获取可靠的结果。
所以如果这个类可能会创建散列表的情况下,重写了equals方法,就必须重写配套的hashcode方法,他们两个在散列表中是搭配使用的。
equals和==和hashcode的恩怨情仇相关推荐
- 漫画:前端发展史的江湖恩怨情仇
作者 | 前端布道师 来源 | 前端布道师(ID:honeyBadger8) 时间总是过得很快, 似乎快得让人忘记了昨天,前端WEB领域的发展更是如此,转眼间已是近30年,时光荏苒,初心不变,在一代又 ...
- 宏观与量子的恩怨情仇
第四章:宏观与量子的恩怨情仇 我们知道哥本哈根诠释由波尔和海森堡于1927年在哥本哈根合作研究时共同提出的.此诠释建立在由德国数学家.物理学家Max Born所提出的"波函数的概率表达&qu ...
- [你必须知道的.NET]第一回:恩怨情仇:is和as
本文将介绍以下内 容: • 类型转换 • is/as操作符小议 1. 引言 类型安全是.NET设计之初重点考虑 的内容之一,对于程序设计者来说,完全把握系统数据的类型安全,经常是力不从心的问题.现在, ...
- java重写6,java重写equals()方法和hashCode()方法
1.equals()方法和hashCode()方法是什么? equals()和hashCode()都是是Java中万物之源Object类中的方法: equals方法用于比较两个对象是否相同,Objec ...
- [你必须知道的.NET] 第一回:恩怨情仇:is和as
发布日期:2007.4.7 作者:Anytao ©2007 Anytao.com 转贴请注明出处,留此信息. 本文将介绍以下内容: • 类型转换 • is/as操作符小议 1. 引言 类型安全是.NE ...
- JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法
在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address {privat ...
- 重写equals()时为什么也得重写hashCode()之深度解读equals方法与hashCode方法渊源
重写equals()时为什么也得重写hashCode()之深度解读equals方法与hashCode方法渊源 在使用Map接口时,我们的愿望是当key1.equals(key2)时,它们获取的valu ...
- 详解 equals() 方法和 hashCode() 方法
来源:编程迷思, www.cnblogs.com/kismetv/p/7191736.html 前言 Java的基类Object提供了一些方法,其中equals()方法用于判断两个对象是否相等,has ...
- 红帽 与 CentOS 之间的恩怨情仇
[CSDN 编者按]红帽正式宣布 CentOS 8 于 2021年底结束支持,后续将由 CentOS Stream 接班.一起来看看红帽与 CentOS 的"恩怨情仇"-- 参考链 ...
最新文章
- libc.so.6 is needed by mysql_libc.so.6(GLIBC_2.14)(64bit) is needed by…问题的解决办法
- LabVIEW保存、读取配置文件
- pta 7-3 两个有序链表序列的合并 (20 分)
- AI会率先在汽车、安全和金融领域落地!不服来辩 | AI科技评论
- 机器学习实战(八)预测数值型数据:回归
- 【ICCV2019】完整论文列表
- 草稿 12月第2周 排课
- ExtJs2.0学习系列(4)--Ext.FormPanel之第一式
- ICCV 2021 oral 重构+预测,双管齐下提升视频异常检测性能
- 中国企业借东博会“走出去”将打造马来西亚首个智慧城市
- iOS - Swift NSUserDefaults		数据存储
- 调用百度API实现人像动漫化(C++)
- yaml和properties文件相互转换的网站
- android代码修改view的宽度,代码动态改变view的大小
- 【复习】物联网导论知识梳理
- vs2019(C语言) 使用教程
- 也谈谈内卷化、996和程序员的发展
- 数独大师级技巧_数独入门:你必须掌握的那些规则和技巧
- JSP SSH校园兼职信息发布平台myeclipse开发mysql数据库MVC模式java编程计算机网页设计
- jedis 源码阅读二——jedisPool
热门文章
- css属性选择器诸如Class^=,Class*= ,Class$=释义
- 景德镇人都应该知道的一个历史人物--唐英
- 做一个派发工单的微信小程序
- 我的QQ微博 和新浪微博地址
- 【零基础学习服装设计】服装色彩搭配试听课_豆瓣
- springboot中配置logback实现打印控制台、写出文件,控制日志级别的方式
- matlab进行电机仿真,MATLAB simulink在电机中的仿真.ppt
- Proteus仿真时出现Cannot open‘***\LISA5476.SDF’的错误!
- kong网关使用记录
- Asp.net Ajax WebService 实现循环引用(自定义JavascriptConverter)