面试最常问的问题

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和hashCodecom.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);    }

本文由传智教育博学谷 - 狂野架构师教研团队公布,转载请注明出处!

如果本文对您有帮忙,欢送关注和点赞;如果您有任何倡议也可留言评论或私信,您的反对是我保持创作的能源