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

  本篇文章为你整理了【面经】被虐了之后,我翻烂了equals源码,总结如下()的详细内容,包含有 【面经】被虐了之后,我翻烂了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.

   * 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比较的就是内存地址

  所以默认情况下,这俩没啥区别

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

  
结论:

  hashCode是给java集合类的一些动作提供支撑,来判断俩对象“是否是同一个”的标准

  equals是给你编码时判断用的,所以,这俩必须保持一致的逻辑。

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

  

 

  本文由传智教育博学谷 - 狂野架构师教研团队发布
 

  如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力
 

  转载请注明出处!

  以上就是【面经】被虐了之后,我翻烂了equals源码,总结如下()的详细内容,想要了解更多 【面经】被虐了之后,我翻烂了equals源码,总结如下的内容,请持续关注盛行IT软件开发工作室。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: