首页 > 面试中Equals和==还分不清?
头像
大黄奔跑
编辑于 2020-12-28 09:49
+ 关注

面试中Equals和==还分不清?

内容目录

面试复现大黄小课堂1. ==解读2. equals含义先看例子,再看代码。1. Object类中的equals()2. 基本类型的equals比较3. String的equals()方法大黄小课堂第二讲1. 源码一窥

面试复现

Hello,大家好,我是你们的老朋友大黄。

今天这个视频开始了一种新的模式,以回顾面试的过程给大家讲解一些面试题目该怎么在面试中让面试官满意,从而成功拿到offer。

可能很多同学在面试过程中对于并发的知识、Spring等核心知识准备的比较充分,而且回答的比较好。

但是当刚开始面试的时候,大部分面试官面试的难度都是由易变难的,相反这些问题才是整场面试的关键,回答的不好,面试官失去了继续聊下去的欲望了。

下面为常见的面试对话

面试官:

大黄同学是吧?你来说一下equals和==之间的区别吧?通常在哪些地方会用上呢?

大黄:
此刻内心波动,好家伙这题我会,我可是早早准备过的。

== 对于不同的类型比较的是不一样的,基础类型对比的是值是否相同,引用类型对比的是引用是否相同;而 equals 则是比较的值是否相同

面试官:

你确定是这样的吗?对于所有的对象equals都是比较的值吗?

大黄:
为啥这么问,这题我准备过的,就是这样的吧。。

我印象中确实是这样的,equals 比较的确实值是否相同。

面试官此刻内心mmp,这也不清楚,赶快结束,我还得回去工作呢。脸上却还是笑嘻嘻的说

嗯,好的,你下去再看看。我们再问问其他的问题

过两天之后,面试结果杳无音信,好点的公司可能会发送一条感谢信。

【xx出行】感谢您对xx机会的关注,诚邀您参与我们的应聘者体验调研,以帮助我们提升招聘体验。链接:https://page.xiaojukeji.com/active/ddpage_0aGzHCjT.html?callback=TWpjNU16UTBOdz09

痛定思痛的大黄决定从此以后牢牢解决这个问题,翻遍天下奇书、搜索各大论坛得到如下真经。

大黄小课堂

1. ==解读

对于基本类型和引用类型 == 的作用效果是不同的,两者比较如下

  • 基本类型:比较的是值是否相同;
  • 引用类型:比较的是引用是否相同;

我们可以来看一个简单的实例:

public static void main(String[] args) {
    String x = "Hello";
    String y = "Hello";
    String z = new String("Hello");

    System.out.println(x==y);   // true
    System.out.println(x==z); // false
}

System.out.println(x==y); 对于引用对象的x和y,指向的是同一个字符串,两个引用是相同的,因此输出true。

System.out.println(x==z); 对于字符串z会指向新new出来的一个字符串对象,x与z指向的不是同一个字符串对象。

具体的内存可以参见下图:

字符串在Jvm中的布局<figcaption style="line&#45;height&#58; inherit&#59; margin&#58; 0px&#59; padding&#58; 0px&#59; margin&#45;top&#58; 10px&#59; text&#45;align&#58; center&#59; color&#58; rgb&#40;153&#44; 153&#44; 153&#41;&#59; font&#45;size&#58; 0&#46;7em&#59;">字符串在Jvm中的布局</figcaption>

2. equals含义

equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了值比较。看下面的代码就明白了,如果没有重写equals方法,默认是按照==进行比较,如果是普通的对象,则比较的是引用类型。

  • 如果是基本类型,则采用的是Object的equals方法进行比较
  • 如果是字符串,String类重写了equals方法,采用String的equals方法比较
  • 如果是一般对象,并且没有重写equals方法,则默认采用的==进行比较

先看例子,再看代码。

public class EqualsAndSame {

    public static void main(String[] args) {
        String x = "Hello";
        String y = "Hello";
        Integer a = 1;
        Integer b = 1;
        // 因为比较的是基本类型,采用的基本数据类型重写的equals方法,比较的是值
        System.out.println(a.equals(b));
        // 因为比较的是字符串,采用的String重写的equals方法,比较的是各个字符串
        System.out.println(x.equals(y)); // true

        Person lisi0 = new Person("lisi",21);
        Person lisi1 = new Person("lisi",21);
        // 因为比较的是对象,因为Person类没有重写自己的equals方法,
        // 因此默认采用Object的equals()方法
        System.out.println(lisi0.equals(lisi1));
    }
}


class Person{

    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

1. Object类中的equals()

equals()如果不被重写,则底层比较方式和==基本一致,底层就是通过==来比较的。

public boolean equals(Object obj) {
  return (this == obj);
}

2. 基本类型的equals比较

/**
 * 本质上比较Integer对象的值
 * @param obj      比较的对象
 * @return
 */

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

3. String的equals()方法

/**
 * 比较两个字符串是否相等
 * 当anObject对象不为空且该对象与比较对象完全相等时返回true
 * @param anObject
 * @return
 */

public boolean equals(Object anObject) {
    // 1. 如果被比较对象与anObject完全相等时则返回true
    if (this == anObject) {
        return true;
    }

    // 判断字符是否是字符串类型
    /**
     * 2.1 首先判断两个字符串长度是否相等,相等则继续比较内部元素;否则直接返回false
     * 2.2 利用while循环依次比较两个数组内部字符是否相等
     */

    if (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;
}

修炼到这种功程度,下次面对这种面试题目,可以这么说:

==对于不同类型比较的是不同的,基础类型对比的是值是否相同,引用类型对比的是引用是否相同;而 equals 对于不同的类型比较的是不同的,对于基础类型因此基础类本身实现了equals()方法,其底层比较的是值是否相同,对于String比较的是字符串内容是否完全相同,而对于其他的类型,如果本身没有重写equals()方法,其比较的是引用是否相同,如果实现了equals()方法,则按照实现方法的逻辑进行比较。

当然回答到这里可能已经很好的回答了面试官的问题,但是这题还没有完,可能面试官还会继续追问。

面试官:

你说一下,一般重写了自己的equals()方法,还需要重写什么方法呢?

大黄:

还需要额外重写hashCode方法。

面试官:

欧,为什么必须要重写hashCode()方法,如果不重写会有什么问题呢?

大黄:

hashCode()主要应用于计算对象的hashCode值,通常会用于一些框架,比如hashMap的key值比较,在hashMap中判断一个key和另一个key是否冲突的时候,就是通过hashCode来计算key的下标。如果重写equals(),而没有重写hashCode()方法时,因为是两个都是new的对象,所以即使里面的值一样,但是对象所处的地址却不同,所以使用默认的hashCode也就不同,当然在hashMap中就不会认为两个是一个对象。这样就会产生set进去了一个对象,拿着同样的key却取不出来。

大黄小课堂第二讲

上面说到了重写equals()方法必须重写hashCode()方法,只是说到了面试中应该怎么回答,如果对于上面回答不是很理解,下面将深入的讲解一番。

1. 源码一窥

hashCode()方法

本身是一个本地方法,也就是说本身是在本地层面进行求解hashCode()的值的,返回值时int。

public native int hashCode();

源码中对于hashCode()也有一些约束,源码备注中的翻译如下:

  1. 如果对象在使用equals方法中进行比较的参数没有修改,那么多次调用一个对象的hashCode()方法返回的哈希值应该是相同的

  2. 如果两个对象通过equals方法比较是相等的,那么要求这两个对象的hashCode方法返回的值也应该是相等的

  3. 如果两个对象通过equals方法比较是不同的,那么也不要求这两个对象的hashCode方法返回的值是不相同的。(换句话说两个对象equals()不相同,这两个对象的hashCode()方法可等可不等)但是我们应该知道对于不同对象产生不同的哈希值对于哈希表(HashMap)能够提高性能。

hashCode()的源码中一直介绍hashMap,那我们可以看看hashMap中对于hashCode()的应用,先看一下HashMap的基本结构(关于hashMap的知识网上有很多知识,此处不再赘述)

hashMap结构<figcaption style="line&#45;height&#58; inherit&#59; margin&#58; 0px&#59; padding&#58; 0px&#59; margin&#45;top&#58; 10px&#59; text&#45;align&#58; center&#59; color&#58; rgb&#40;153&#44; 153&#44; 153&#41;&#59; font&#45;size&#58; 0&#46;7em&#59;">hashMap结构</figcaption>

(图片来自于美团技术团队)

那么是如何确定一个数据存储在数组中的哪个位置呢?就是通过hashCode方法进行计算出存储在哪个位置,上面hashCode()规则时说了有可能两个不同对象的hashCode方法返回的值相同,那么此时就会产生冲突,产生冲突的话就会调用equals方法进行比对,如果不同,那么就将其加入链表,如果相同就替换原数据。

本身hashMap容器重写了HashCode()方法的,但是本质上也是调用Object的hashCode()方法

public final int hashCode() {
  return Objects.hashCode(key) ^ Objects.hashCode(value);
}

如果自定义对象作为key但是不重写hashCode()方***怎么样呢?

先看一下没有重写hashCode()的对象
Person定义如下:

class Person{

    private String name;
    private Integer age;
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}

public class EqualsAndSame {

    public static void main(String[] args) {
        Map<Person, Integer> personMap = new HashMap<>();
        Person person1 = new Person("zhangsan",12);
        Person person3 = new Person("zhangsan",12);
        personMap.put(person2, 2222);
        System.out.println(personMap.get(person3));
    }
}

输出结果出人意料确实null,没有按照预获取到2222,为啥会这样呢?

HashMap是采用hashCode()用来计算该对象放入数组中的哪个位置,两个都是重写new的对象,但是由于没有重写,默认都是采用ObjecthashCode(),虽然里面的内容都相同,但是对象所处的地址却不同,所以使用默认的hashCode也就不同,get()方法就以为是获取另一个key值。返回的自然是null

如何解决这个问题呢?
可以重写equals()方法和hashCode()方法,可以通过Idea快捷键自动生成equals()hashCode()方法

class Person{
    ...
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return Objects.equals(name, person.name) &&
                Objects.equals(age, person.age);
    }

    /**
     * 重写的hashCode()方法是通过对象的全部字段来计算hashCode值
     * 最底层是通过Arrays的hashCode()来计算
     * @return
     */

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

Objects.hash()底层是通过Arrays.hashCode()实现的

public static int hashCode(Object a[]) {
    if (a == null)
        return 0;
    int result = 1;
    // 遍历对象的每个字段,通过字段的hashCode()方法累加得到
    for (Object element : a)
        result = 31 * result + (element == null ? 0 : element.hashCode());

    return result;
}

重写了之后,两个对象只要各个字段的值相同,对象的hashCode值必定相同,这也就是为什么重写equals()方法的时候要求必须重写hashCode()方法了

好了,今天的大黄每日成长技能到此结束了。感谢各位大佬的观看,下篇再见。

平时不积累,面试空流泪。

全部评论

(1) 回帖
加载中...
话题 回帖

相关热帖

近期热帖

近期精华帖

热门推荐