Java基础面试专栏(六):==与equals()的区别,面试高频易错点全解析

3 阅读11分钟

承接前五篇专栏,我们先后拆解了Java数据类型、抽象类与接口、final关键字、static关键字,以及String、StringBuffer、StringBuilder的核心区别,今天继续聚焦Java基础面试的高频重点——==与equals()的区别。这两个是Java中最基础的比较方式,日常开发和面试中高频出现,但很多面试者容易混淆两者的适用场景,尤其是在String、包装类中使用时,经常踩坑;甚至不清楚equals()的默认实现和重写规则,今天我们就从面试答题角度,把两者的区别、底层逻辑、用法和易错点拆透,帮你快速掌握答题思路,轻松应对追问。

先给大家一个面试万能总结(一句话直达核心,适合开场快速应答):==是操作符,用于基本数据类型时比较数值是否相等,用于引用数据类型时比较内存地址是否相同;equals()是Object类的方法,默认实现等同于==,但常被重写(如String、包装类),重写后用于比较对象内容是否逻辑相等;基本类型只能用==比较,对象类型需根据需求选择——判断是否为同一对象用==,判断内容是否相等用equals()(需确保正确重写)。

一、核心对比:一张表分清两者核心差异(面试直接套用)

面试中,当被问到==与equals()的区别时,先给出核心对比表,再逐一拆解,会显得答题有条理、思路清晰。以下是两者核心特性对比,覆盖面试所有高频考点,建议牢记:

核心特性==equals()
本质Java中的比较操作符Object类中定义的成员方法,可重写
比较内容基本类型:比较数值是否相等;引用类型:比较内存地址是否相同默认:比较内存地址(等同于==);重写后:比较对象内容是否逻辑相等
可修改性不可修改,行为固定可重写,根据业务需求定义比较逻辑
适用场景1. 基本数据类型的数值比较;2. 引用数据类型判断是否指向同一对象引用数据类型的内容逻辑相等比较(需正确重写)
使用注意不能用于引用类型的内容比较;基本类型无equals()方法未重写时等同于==;重写后需遵循规范,同步重写hashCode()

二、逐一拆解:==与equals()的用法+代码示例

掌握对比表后,面试中还会追问两者的具体用法、底层实现和重写规则,我们结合代码示例,逐一拆解,帮你吃透本质,避免死记硬背,同时覆盖日常开发中的常见场景。

1. ==的用法:分基本类型和引用类型,行为不同

==的核心作用是“比较值”或“比较内存地址”,具体行为取决于操作的是基本数据类型还是引用数据类型,这是最基础也是最容易区分的点。

(1)基本数据类型:比较数值是否相等

Java中的8种基本数据类型(byte、short、int、long、float、double、char、boolean),使用==比较时,直接判断两者的数值是否一致,与内存地址无关——因为基本数据类型直接存储在栈内存中,不存在“引用”的概念。

代码示例(结合开发中“基本类型判断”场景):

public class EqualOperatorTest {
    public static void main(String[] args) {
        // 整数类型比较
        int num1 = 100;
        int num2 = 100;
        long num3 = 100L;
        System.out.println(num1 == num2); // true(数值相等)
        System.out.println(num1 == num3); // true(数值相等,自动类型转换)
        
        // 字符类型比较(本质是比较ASCII码值)
        char c1 = 'A';
        char c2 = 65; // 'A'的ASCII码值为65
        char c3 = 'B';
        System.out.println(c1 == c2); // true(ASCII码值相等)
        System.out.println(c1 == c3); // false(ASCII码值不等)
        
        // 布尔类型比较
        boolean flag1 = true;
        boolean flag2 = true;
        System.out.println(flag1 == flag2); // true(数值相等)
        
        // 浮点类型比较(注意:浮点型存在精度问题,不建议用==)
        float f1 = 0.1f;
        double f2 = 0.1;
        System.out.println(f1 == f2); // false(精度差异导致)
    }
}

面试重点:浮点类型(float、double)不建议用==比较,因为存在精度丢失问题(如0.1f和0.1的底层存储值不同),若需比较浮点型,建议使用Math.abs()判断差值是否在可接受范围内。

(2)引用数据类型:比较内存地址是否相同

引用数据类型(如String、自定义类、数组等),变量存储的是对象的“内存地址”(引用),使用==比较时,判断的是两个引用是否指向同一个对象,即内存地址是否一致,与对象内容无关。

代码示例(结合开发中“对象引用判断”场景):

// 自定义用户类(未重写equals())
class User {
    private String username;
    private int age;
    
    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
}

public class ReferenceCompareTest {
    public static void main(String[] args) {
        // 场景1:两个不同对象,内容相同但地址不同
        User user1 = new User("张三", 22);
        User user2 = new User("张三", 22);
        System.out.println(user1 == user2); // false(不同对象,地址不同)
        
        // 场景2:两个引用指向同一个对象
        User user3 = user1;
        System.out.println(user1 == user3); // true(同一对象,地址相同)
        
        // 场景3:String对象的==比较(结合常量池特性)
        String str1 = "Java面试";
        String str2 = "Java面试";
        String str3 = new String("Java面试");
        System.out.println(str1 == str2); // true(常量池复用,地址相同)
        System.out.println(str1 == str3); // false(new创建新对象,地址不同)
    }
}

面试重点:String的==比较需结合常量池特性——字面量创建的String对象会存入常量池,复用已有对象(地址相同);new关键字创建的String对象存入堆内存,地址不同,这也是面试中高频结合的考点。

2. equals()的用法:默认等同于==,重写后比较内容

equals()是Object类中定义的方法,所有Java类都继承了这个方法,其默认实现就是使用==比较内存地址;但在实际开发中,我们通常需要比较对象的“内容是否相等”,因此会重写equals()方法,自定义比较逻辑。

(1)默认行为:未重写equals(),等同于==

Object类中equals()的源码如下(核心就是用==比较内存地址):

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

如果自定义类未重写equals(),调用equals()时,本质就是用==比较内存地址,与==的效果完全一致。

代码示例:

// 沿用上面的User类(未重写equals())
public class EqualsDefaultTest {
    public static void main(String[] args) {
        User user1 = new User("张三", 22);
        User user2 = new User("张三", 22);
        User user3 = user1;
        
        // 未重写equals(),等同于==
        System.out.println(user1.equals(user2)); // false(地址不同)
        System.out.println(user1.equals(user3)); // true(地址相同)
    }
}

(2)重写后行为:比较对象内容是否逻辑相等

日常开发中,我们通常希望“内容相同的对象,equals()返回true”,因此需要重写equals()方法,自定义比较逻辑(如比较对象的属性值)。最典型的就是String类,它重写了equals()方法,用于比较字符串的内容,而非内存地址。

代码示例1(String类的equals()用法):

public class StringEqualsTest {
    public static void main(String[] args) {
        String str1 = new String("Java面试");
        String str2 = new String("Java面试");
        String str3 = new String("Java干货");
        
        // String重写了equals(),比较内容
        System.out.println(str1.equals(str2)); // true(内容相同)
        System.out.println(str1.equals(str3)); // false(内容不同)
        
        // 与==对比
        System.out.println(str1 == str2); // false(地址不同)
    }
}

代码示例2(自定义类重写equals()):

重写equals()时,需遵循4个核心规则(面试高频考点):① 一致性:若a.equals(b)为true,则b.equals(a)必须为true;② 传递性:若a.equals(b)和b.equals(c)为true,则a.equals(c)必须为true;③ 非空性:a.equals(null)必须返回false;④ 与hashCode()同步:若两个对象equals()为true,它们的hashCode()必须相同(否则会导致HashMap等集合存储异常)。

import java.util.Objects;

// 自定义用户类,重写equals()和hashCode()
class User {
    private String username;
    private int age;
    
    public User(String username, int age) {
        this.username = username;
        this.age = age;
    }
    
    // 重写equals(),比较对象属性(内容)
    @Override
    public boolean equals(Object o) {
        // 1. 同一对象,直接返回true
        if (this == o) return true;
        // 2. 对象为null或类型不同,返回false
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 类型转换,比较属性值
        User user = (User) o;
        return age == user.age && Objects.equals(username, user.username);
    }
    
    // 同步重写hashCode(),与equals()保持一致
    @Override
    public int hashCode() {
        return Objects.hash(username, age);
    }
}

public class EqualsRewriteTest {
    public static void main(String[] args) {
        User user1 = new User("张三", 22);
        User user2 = new User("张三", 22);
        User user3 = new User("李四", 23);
        
        // 重写后,equals()比较内容
        System.out.println(user1.equals(user2)); // true(内容相同)
        System.out.println(user1.equals(user3)); // false(内容不同)
        
        // 与==对比
        System.out.println(user1 == user2); // false(地址不同)
    }
}

三、面试核心追问:重写equals()为什么要同步重写hashCode()?(加分项)

这是面试中高频追问的知识点,很多面试者只知道“要重写”,却讲不清原因,掌握这个知识点,能让你的答题更有深度,轻松加分。

核心原因:为了保证HashMap、HashSet等基于哈希表的集合能正常工作

集合的底层逻辑:HashMap存储元素时,先通过hashCode()计算对象的哈希值,确定元素在哈希表中的位置;再通过equals()判断该位置是否已有相同内容的对象——若两个对象equals()为true,说明内容相同,应视为同一个对象,若它们的hashCode()不同,会被存储在哈希表的不同位置,导致集合中出现重复元素,违背集合的去重特性。

通俗理解:hashCode()是“地址指引”,equals()是“内容校验”;指引相同,才会去校验内容;若内容相同但指引不同,就会被当成两个不同对象,导致集合异常。

反例(未同步重写hashCode()):

import java.util.HashMap;
import java.util.Map;

class Student {
    private String id;
    
    public Student(String id) {
        this.id = id;
    }
    
    // 只重写equals(),未重写hashCode()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return Objects.equals(id, student.id);
    }

    // 未重写hashCode(),使用Object类的默认实现(基于内存地址)
}

public class HashCodeTest {
    public static void main(String[] args) {
        Student s1 = new Student("1001");
        Student s2 = new Student("1001");
        
        Map<Student, String> studentMap = new HashMap<>();
        studentMap.put(s1, "张三");
        studentMap.put(s2, "张三");
        
        // 预期:map中只有一个元素(因为s1和s2内容相同)
        // 实际:map中有两个元素(因为hashCode()不同,被视为不同对象)
        System.out.println(studentMap.size()); // 输出:2
    }
}

结论:重写equals()时,必须同步重写hashCode(),确保“equals()为true的两个对象,hashCode()一定相同”;反之,hashCode()相同的两个对象,equals()不一定为true(哈希碰撞)。

四、高频易错点大汇总(必记,避开面试陷阱)

==与equals()的面试易错点,主要集中在“适用场景混淆”“String和包装类的特殊情况”“重写规则”,记住以下5点,轻松避开所有陷阱:

  1. 易错点1:用==比较引用类型的内容——==用于引用类型时,只比较内存地址,无论内容是否相同,只要不是同一个对象,就返回false;比较内容必须用equals()(需重写)。

  2. 易错点2:用equals()比较基本类型——基本数据类型(int、char等)没有equals()方法,若尝试使用(如10.equals(20)),会编译报错,只能用==比较。

  3. 易错点3:String的equals()与==混淆——字面量创建的String对象,==可能返回true(常量池复用);new创建的String对象,==一定返回false,equals()才会比较内容。

  4. 易错点4:重写equals()未重写hashCode()——会导致HashMap、HashSet等集合存储异常,出现重复元素,违背集合的设计逻辑。

  5. 易错点5:浮点型用==比较——浮点型(float、double)存在精度丢失问题,直接用==比较可能出现误判,建议用Math.abs(差值) < 允许误差(如1e-6)的方式判断。

五、面试总结与延伸

  1. 答题逻辑:先一句话总结==与equals()的核心区别,再给出对比表,接着分“==的用法”“equals()的用法(默认+重写)”逐一拆解,补充重写规则和hashCode()的关联,最后总结易错点,答题全面且有条理,符合面试答题习惯。

  2. 高频面试题(提前准备,直接应答):

① ==与equals()的区别?(核心考点,按本文对比表+用法应答)

② equals()的默认实现是什么?重写时需要遵循哪些规则?(默认等同于==,4个核心规则)

③ 重写equals()为什么要同步重写hashCode()?(保证集合正常工作,避免重复元素)

④ String类的equals()和==有什么区别?(equals()比较内容,==比较地址,结合常量池特性)