当前位置:网站首页>【面经】被虐了之后,我翻烂了equals源码,总结如下
【面经】被虐了之后,我翻烂了equals源码,总结如下
2022-08-04 00:05:00 【博学谷狂野架构师】
面试最常问的问题
1、equals比较的什么?
2、有没有重写过equals?
3、有没有重写过hashCode?
4、什么情况下需要重写equals()和hashCode()?
1) equals源码
目标:如果不做任何处理(可能绝大大大多数场景的对象都是这样的),jvm对同一个对象的判断逻辑是怎样的
我们先读一下Object里的源码:
/**
* Indicates whether some other object is "equal to" this one.
* <p>
* The {@code equals} method implements an equivalence relation
* on non-null object references:
* <ul>
* <li>It is <i>reflexive</i>: for any non-null reference value
* {@code x}, {@code x.equals(x)} should return
* {@code true}.
* <li>It is <i>symmetric</i>: for any non-null reference values
* {@code x} and {@code y}, {@code x.equals(y)}
* should return {@code true} if and only if
* {@code y.equals(x)} returns {@code true}.
* <li>It is <i>transitive</i>: for any non-null reference values
* {@code x}, {@code y}, and {@code z}, if
* {@code x.equals(y)} returns {@code true} and
* {@code y.equals(z)} returns {@code true}, then
* {@code x.equals(z)} should return {@code true}.
* <li>It is <i>consistent</i>: for any non-null reference values
* {@code x} and {@code y}, multiple invocations of
* {@code x.equals(y)} consistently return {@code true}
* or consistently return {@code false}, provided no
* information used in {@code equals} comparisons on the
* objects is modified.
* <li>For any non-null reference value {@code x},
* {@code x.equals(null)} should return {@code false}.
* </ul>
* <p>
* 该方法用于识别两个对象之间的相似性
* 也就是说,对于一个非null值,x和y,当且仅当它们指向同一个对象时才会返回true
* 言外之意,和==没啥两样。
* The {@code equals} method for class {@code Object} implements
* the most discriminating possible equivalence relation on objects;
* that is, for any non-null reference values {@code x} and
* {@code y}, this method returns {@code true} if and only
* if {@code x} and {@code y} refer to the same object
* ({@code x == y} has the value {@code true}).
* <p>
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
猜想:如果我们不做任何操作,equals将继承object的方法,那么它和==也没啥区别!
下面一起做个面试题,验证一下这个猜想:
package com.eq;
import java.io.InputStream;
public class DefaultEq {
String name;
public DefaultEq(String name){
this.name = name;
}
public static void main(String[] args) {
DefaultEq eq1 = new DefaultEq("张三");
DefaultEq eq2 = new DefaultEq("张三");
DefaultEq eq3 = eq1;
//虽然俩对象外面看起来一样,eq和==都不行
//因为我们没有改写equals,它使用默认object的,也就是内存地址
System.out.println(eq1.equals(eq2));
System.out.println(eq1 == eq2);
System.out.println("----");
//1和3是同一个引用
System.out.println(eq1.equals(eq3));
System.out.println(eq1 == eq3);
System.out.println("===");
//以上是对象,再来看基本类型
int i1 = 1;
Integer i2 = 1;
Integer i = new Integer(1);
Integer j = new Integer(1);
Integer k = new Integer(2);
//只要是基本类型,不管值还是包装成对象,都是直接比较大小
System.out.println(i.equals(i1)); //比较的是值
System.out.println(i==i1); //拆箱 ,
// 封装对象i被拆箱,变为值比较,1==1成立
//相当于 System.out.println(1==1);
System.out.println(i.equals(j)); //
System.out.println(i==j); // 比较的是地址,这是俩对象
System.out.println(i2 == i); // i2在常量池里,i在堆里,地址不一样
System.out.println(i.equals(k)); //1和2,不解释
}
}
结论:
“==”比较的是什么?
用于基本数据(8种)类型(或包装类型)相互比较,比较二者的值是否相等。
用于引用数据(类、接口、数组)类型相互比较,比较二者地址是否相等。
equals比较的什么?
默认情况下,所有对象继承Object,而Object的equals比较的就是内存地址
所以默认情况下,这俩没啥区别
2) 内存地址生成与比较
tips:既然没区别,那我们看一下,内存地址到底是个啥玩意
目标:内存地址是如何来的?
Main.java
public static void main(String[] args) {
User user1=new User("张三");
User user2=new User("张三");
}
1、加载过程(回顾)
从java文件到jvm:
tips: 加载到方法区
这个阶段只是User类的信息进入方法区,还没有为两个user来分配内存
2、分配内存空间
在main线程执行阶段,指针碰撞(连续内存空间时),或者空闲列表(不连续空间)方式开辟一块堆内存
每次new一个,开辟一块,所以两个new之间肯定不是相同地址,哪怕你new的都是同一个类型的class。
那么它如何来保证内存地址不重复的呢?(cas画图)
3、指向
在栈中创建两个局部变量 user1,user2,指向堆里的内存
归根到底,上面的==比较的是两个对象的堆内存地址,也就是栈中局部变量表里存储的值。
public boolean equals(Object obj) {
return (this == obj);//本类比较的是内存地址(引用)
}
3) 默认equals的问题
需求(or 目标):user1和user2,如果name一样我们就认为是同一个人;如何处理?
tips:
面试最常问的问题
1、equals比较的什么?
2、有没有重写过equals?
3、有没有重写过hashCode?
4、什么情况下需要重写equals()和hashCode()?
1、先拿User下手,看看它的默认行为(com.eq.EqualsObjTest)
public static void main(String[] args) {
//需求::user1和user2,在现实生活中是一个人;如何判定是一个人(相等)
User user1 = new User("张三");
User user2 = new User("张三");
System.out.println("是否同一个人:"+user1.equals(user2));
System.out.println("内存地址相等:"+String.valueOf(user1 == user2));//内存地址
System.out.println("user1的hashCode为>>>>" + user1.hashCode());
System.out.println("user2的hashCode为>>>>" + user2.hashCode());
}
输出如下
结论:
很显然,默认的User继承了Object的方法,而object,根据上面的源码分析我们知道,equals就是内存地址。
而你两次new User,不管name怎么一致,内存分配,肯定不是同一个地址!
怎么破?
2、同样的场景,我们把用户名从User换成单纯的字符串试试(com.eq.EqualsStrTest)
public static void main(String[] args) {
String str1 = "张三";//常量池
String str2 = new String("张三");//堆中
String str3 = new String("张三");//堆中
System.out.println("是否同一人:"+str1.equals(str2));//这个地方为什么相等呢,重写
System.out.println("是否同一人:"+str2.equals(str3));//这个地方为什么相等呢,重写
//如果相等,hashcode必须相等,重写
System.out.println("str1的hashCode为>>" + str1.hashCode());
System.out.println("str2的hashCode为>>" + str2.hashCode());
}
}
输出如下
达到了我们的逾期,相同的name,被判定为同一个人,为什么呢?往下看!
String的源码分析
/**
* Compares this string to the specified object. The result is {@code
* true} if and only if the argument is not {@code null} and is a {@code
* String} object that represents the same sequence of characters as this
* object.
*
* @param anObject
* The object to compare this {@code String} against
*
* @return {@code true} if the given object represents a {@code String}
* equivalent to this string, {@code false} otherwise
*
* @see #compareTo(String)
* @see #equalsIgnoreCase(String)
*/
public boolean equals(Object anObject) {
//如果内存地址相等,那必须equal
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
//如果对象是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) {
//从前往后,任意一个字符不匹配,直接返回false
if (v1[i] != v2[i])
return false;
i++;
}
//全部匹配结束,返回true
return true;
}
}
return false;
}
结论:
String类型改写了equals方法,没有使用Object的默认实现
它不管你是不是同一个内存地址,只要俩字符串里的字符都匹配上,那么equals就认为它是true
3、据此,我们参照String,来重写User的equals和hashCode(com.eq.User2)
@Override
public boolean equals(Object o) {
//注意这些额外的判断类操作
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
//比较值
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
//返回值的hashCode
return name != null ? name.hashCode() : 0;
}
换成User2 再来跑试试 (参考 com.eq.EqualsObjTest2)
目的达到!
4)hashCode与equals
为什么说hashCode和equals是一对搭档?他俩到底啥关系需要绑定到一块?
看代码说话:(com.eq.Contains)
package com.eq;
import java.util.HashSet;
import java.util.Set;
public class Contains {
public static void main(String[] args) {
User user1 = new User("张三");
User user2 = new User("张三");
Set set = new HashSet();
set.add(user1);
System.out.println(set.contains(user2));
User2 user3 = new User2("张三");
User2 user4 = new User2("张三");
Set set2 = new HashSet();
set2.add(user3);
System.out.println(set2.contains(user4));
}
}
结论:
hashCode是给java集合类的一些动作提供支撑,来判断俩对象“是否是同一个”的标准
equals是给你编码时判断用的,所以,这俩必须保持一致的逻辑。
5)总结
1、特殊业务需求需要重写,比如上面的
2、例如map,key放自定义对象也需要重写
3、重写equals后必须要重写hashCode,要保持逻辑上的一致!
1.2.5 关于双等(扩展)
equals被重写后,双等还留着干啥用?
1)String的特殊性
tips:面试常问的问题
intern是做什么的?
先来看一段代码:(com.eq.Intern)
public class Intern {
public static void main(String[] args) {
String str1 = "张三";//常量池
String str2 = new String("张三");//堆中
//intern;内存地址是否相等(面试常问)
System.out.println("str1与str2是否相等>>" +(str1==str2)); // false
System.out.println("str1与str2是否相等>>" +(str1==str2.intern())); // true
}
}
版本声明:(JDK1.8)
new String是在堆上创建字符串对象。 当调用 intern() 方法时, JVM会将字符串添加(堆引用指向常量池)到常量池中
注意:
1、1.8版本只是将hello word在堆中的引用指向常量池,之前的版本是把hello word复制到常量池
2、堆(字符串常量值) 方法区(运行时常量池)不要搞反了
2)valueOf里的秘密
关于双等号地址问题,除了String.intern() , 在基础类型里,如Integer,Long等同样有一个方法:valueOf需要注意
我们先来看一个小例子: 猜一猜结果?
package com.eq;
public class Valueof {
public static void main(String[] args) {
System.out.println( Integer.valueOf(127) == Integer.valueOf(127));
System.out.println( Integer.valueOf(128) == Integer.valueOf(128));
}
}
奇怪的结果……
源码分析(以Integer为例子):
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* !在-128 到 127 之间会被cache,同一个地址下,超出后返回new对象!
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
专注Java技术干货分享,欢迎志同道合的小伙伴,一起交流学习
边栏推荐
- Go编译原理系列7(Go源码调试)
- Jmeter-参数化
- JS获得URL超链接的参数值
- After building the pytorch environment, the pip and conda commands cannot be used
- Nanoprobes 棕榈酰纳米金相关说明书
- The world's first mass production, with the most fixed points!How does this AVP Tier1 lead?
- YOLOv7改进之二十二:涨点神器——引入递归门控卷积(gnConv)
- LeetCode 19:删除链表的倒数第 N 个结点
- Talking about the future development direction of my country's industrial parks
- 一文搞定 SQL Server 执行计划
猜你喜欢
2022年8月份DAMA-CDGA/CDGP数据治理认证招生简章
响应式织梦模板餐饮酒店类网站
POE交换机全方位解读(下)
Spinnaker调用Jenkins API 返回403错误
FinClip, help smart TV more imagination
Graph-node:创建一个新的subgraph
利用matlab求解线性优化问题【基于matlab的动力学模型学习笔记_11】
超级完美版布局有快捷键,有背景置换
我的祖国
The Chinese Valentine's Day event is romantically launched, don't let the Internet slow down and miss the dark time
随机推荐
689. 三个无重叠子数组的最大和
"Miscellaneous" barcode by Excel as a string
【MySQL —— 索引】
RSS订阅微信公众号初探-feed43
一文搞定 SQL Server 执行计划
2021年数据泄露成本报告解读
In V8 how arrays (with source code, picture and text easier to understand)
OpenCV 图像拼接
Why Flutter Flutter of tutorials is the best choice for business?
2022/8/3 Exam Summary
响应式织梦模板塑身瑜伽类网站
C语言实验十四 结构体
建木DevOps流程的快速运用
rsync 基础用法
机器学习——库
20年将投资美国约2000亿美元,三星电子财大气粗的样子真好看
做项目一定用得到的NLP资源【分类版】
智能管理PoE交换机
YOLOv7改进之二十二:涨点神器——引入递归门控卷积(gnConv)
伦敦银最新均线分析系统怎么操作?