【面经】被虐了之后,我翻烂了equals源码,总结如下

网友投稿 586 2022-10-24

【面经】被虐了之后,我翻烂了equals源码,总结如下

【面经】被虐了之后,我翻烂了equals源码,总结如下

面试最常问的问题1、equals比较的什么?2、有没有重写过equals?3、有没有重写过hashCode?4、什么情况下需要重写equals()和hashCode()?

1) equals源码

目标:如果不做任何处理(可能绝大大大多数场景的对象都是这样的),jvm对同一个对象的判断逻辑是怎样的

我们先读一下Object里的源码:

/** * Indicates whether some other object is "equal to" this one. *

* The {@code equals} method implements an equivalence relation * on non-null object references: *

    *
  • It is reflexive: for any non-null reference value * {@code x}, {@code x.equals(x)} should return * {@code true}. *
  • It is symmetric: 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}. *
  • It is transitive: 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}. *
  • It is consistent: 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. *
  • For any non-null reference value {@code x}, * {@code x.equals(null)} should return {@code false}. *
*

* 该方法用于识别两个对象之间的相似性 * 也就是说,对于一个非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}). *

* 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 }}

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); }

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:springboot页面国际化配置指南
下一篇:ThinkCMF Extend- 基于 ThinkPHP 的内容管理框架
相关文章

 发表评论

暂时没有评论,来抢沙发吧~