欢迎光临
我们一直在努力

【Java八股|第1篇】== 和 equals 的区别

前言

从这一篇开始,我们进入 Java 面试八股系列。

这一系列不只适合面试前背题,也适合用来补 Java 基础。很多问题看起来简单,但是如果只会一句“== 比较地址,equals 比较内容”,面试时很容易被继续追问。

比如面试官可能会问:

  • == 和 equals 有什么区别?
  • String 使用 == 比较为什么可能是 true?
  • equals 默认比较的是什么?
  • 为什么重写 equals 时通常也要重写 hashCode?
  • 基本数据类型能不能调用 equals?

这一篇我们就把 == 和 equals 从基础到面试回答完整梳理一遍。

一、先给结论

可以先记住一句话:

== 比较的是值。对于基本数据类型,比较的是具体数值;对于引用数据类型,比较的是对象地址。
equals 是 Object 类中的方法,默认比较对象地址,但很多类会重写它,用来比较对象内容。

这句话里面有几个重点:

  • == 是运算符
  • equals 是方法
  • 基本数据类型没有 equals
  • 引用类型使用 == 比较地址
  • equals 默认也比较地址
  • String、Integer 等类重写了 equals
  • 下面逐个来看。

    二、== 比较基本数据类型

    对于基本数据类型,== 比较的是具体的值。

    比如:

    public class Test {
    public static void main(String[] args) {
    int a = 10;
    int b = 10;

    System.out.println(a == b);
    }
    }

    输出结果:

    true

    原因很简单,a 和 b 都是 int 类型,并且值都是 10。

    再看一个例子:

    public class Test {
    public static void main(String[] args) {
    char c = 'A';
    int num = 65;

    System.out.println(c == num);
    }
    }

    输出结果:

    true

    因为字符 'A' 对应的编码值就是 65,所以比较结果为 true。

    这里说明一点:

    基本数据类型使用 == 时,比较的是变量中保存的具体值。

    常见基本数据类型包括:

    byte
    short
    int
    long
    float
    double
    char
    boolean

    它们不是对象,所以不能直接调用 equals 方法。

    三、== 比较引用数据类型

    引用数据类型使用 == 时,比较的是两个引用变量保存的地址是否相同。

    先看一个例子:

    public class Test {
    public static void main(String[] args) {
    Student s1 = new Student("张三");
    Student s2 = new Student("张三");

    System.out.println(s1 == s2);
    }
    }

    class Student {
    private String name;

    public Student(String name) {
    this.name = name;
    }
    }

    输出结果:

    false

    虽然两个学生的姓名都是 张三,但是它们是通过两次 new 创建出来的两个对象。

    Student s1 = new Student("张三");
    Student s2 = new Student("张三");

    这两个对象在堆内存中的地址不同,所以:

    s1 == s2

    结果是 false。

    再看另一个例子:

    public class Test {
    public static void main(String[] args) {
    Student s1 = new Student("张三");
    Student s2 = s1;

    System.out.println(s1 == s2);
    }
    }

    输出结果:

    true

    因为:

    Student s2 = s1;

    这行代码不是创建新对象,而是让 s2 指向 s1 指向的同一个对象。

    所以 s1 和 s2 保存的对象地址相同,比较结果就是 true。

    四、equals 默认比较什么?

    equals 是 Object 类中的方法。

    所有 Java 类都直接或间接继承自 Object,所以所有对象都可以调用 equals 方法。

    Object 类中 equals 的默认逻辑可以简单理解为:

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

    也就是说,如果一个类没有重写 equals 方法,那么它默认还是比较对象地址。

    比如:

    public class Test {
    public static void main(String[] args) {
    Student s1 = new Student("张三");
    Student s2 = new Student("张三");

    System.out.println(s1.equals(s2));
    }
    }

    class Student {
    private String name;

    public Student(String name) {
    this.name = name;
    }
    }

    输出结果:

    false

    原因是 Student 类没有重写 equals,所以调用的是 Object 默认的 equals 方法,本质上还是比较地址。

    五、String 中的 equals 为什么比较内容?

    String 类重写了 equals 方法,所以它比较的是字符串内容。

    比如:

    public class Test {
    public static void main(String[] args) {
    String s1 = new String("hello");
    String s2 = new String("hello");

    System.out.println(s1 == s2);
    System.out.println(s1.equals(s2));
    }
    }

    输出结果:

    false
    true

    解释一下:

    s1 == s2

    比较的是两个 String 对象的地址。因为它们是两次 new 出来的对象,所以地址不同,结果是 false。

    s1.equals(s2)

    比较的是字符串内容。两个字符串内容都是 hello,所以结果是 true。

    这就是面试中最常见的一组对比。

    六、String 使用 == 为什么有时是 true?

    看下面这个例子:

    public class Test {
    public static void main(String[] args) {
    String s1 = "hello";
    String s2 = "hello";

    System.out.println(s1 == s2);
    }
    }

    输出结果:

    true

    为什么这里 == 是 true?

    因为字符串字面量会放入字符串常量池。

    String s1 = "hello";
    String s2 = "hello";

    第一次出现 "hello" 时,会在字符串常量池中创建这个字符串。

    第二次再写 "hello" 时,会直接复用常量池中已有的字符串对象。

    所以 s1 和 s2 指向的是同一个对象,使用 == 比较结果就是 true。

    但是如果写成:

    String s1 = new String("hello");
    String s2 = new String("hello");

    System.out.println(s1 == s2);

    输出就是:

    false

    因为 new String("hello") 会在堆中创建新的字符串对象。

    七、自己定义类时如何重写 equals?

    如果我们希望两个 Student 对象只要姓名相同,就认为它们相等,可以重写 equals。

    import java.util.Objects;

    public class Student {
    private String name;
    private Integer age;

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

    @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(name, student.name)
    && Objects.equals(age, student.age);
    }

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

    测试代码:

    public class Test {
    public static void main(String[] args) {
    Student s1 = new Student("张三", 18);
    Student s2 = new Student("张三", 18);

    System.out.println(s1 == s2);
    System.out.println(s1.equals(s2));
    }
    }

    输出结果:

    false
    true

    说明:

    s1 == s2

    依旧比较地址,所以是 false。

    s1.equals(s2)

    调用的是我们重写后的 equals 方法,比较的是姓名和年龄,所以是 true。

    八、为什么重写 equals 通常也要重写 hashCode?

    这是面试中很容易追问的点。

    简单来说:

    如果两个对象通过 equals 判断相等,那么它们的 hashCode 也应该相等。

    这和 HashMap、HashSet 这类集合有关。

    比如我们把两个内容相同的 Student 放入 HashSet:

    Set<Student> set = new HashSet<>();

    set.add(new Student("张三", 18));
    set.add(new Student("张三", 18));

    System.out.println(set.size());

    如果只重写 equals,不重写 hashCode,可能会导致集合认为它们是两个不同对象。

    因为 HashSet 判断元素是否重复时,不只是看 equals,还会用到 hashCode。

    所以实际开发中,只要重写 equals,通常就要一起重写 hashCode。

    现在很多 IDE 都可以自动生成这两个方法。

    九、基本数据类型能不能调用 equals?

    不能。

    比如:

    int a = 10;
    int b = 10;

    System.out.println(a.equals(b));

    这是错误写法。

    因为 int 是基本数据类型,不是对象,没有方法。

    如果是包装类,就可以调用 equals:

    Integer a = 10;
    Integer b = 10;

    System.out.println(a.equals(b));

    输出结果:

    true

    这里的 Integer 是包装类,是对象,所以可以调用 equals。

    不过包装类还涉及自动装箱、缓存机制等问题,后面可以单独讲。

    十、常见面试回答

    如果面试官问:

    == 和 equals 有什么区别?

    可以这样回答:

    == 是运算符,equals 是方法。

    如果比较的是基本数据类型,== 比较的是具体的值。

    如果比较的是引用数据类型,== 比较的是两个对象的内存地址,也就是是否指向同一个对象。

    equals 是 Object 类中的方法,默认实现和 == 一样,也是比较地址。但是很多类会重写 equals,比如 String、Integer,它们重写后通常比较的是对象内容。

    所以在比较字符串内容时,应该使用 equals,而不是使用 ==。

    十一、常见问题总结

    1. String 比较内容应该用什么?

    应该用:

    s1.equals(s2)

    如果担心空指针,可以写成:

    "hello".equals(s1)

    这样即使 s1 是 null,也不会报空指针异常。

    2. equals 一定比较内容吗?

    不一定。

    如果类没有重写 equals,默认还是比较地址。

    只有重写了 equals,才可能按照内容比较。

    3. == 一定比较地址吗?

    不一定。

    如果是基本数据类型,== 比较的是值。

    如果是引用数据类型,== 比较的是地址。

    4. 为什么两个 new String 使用 == 是 false?

    因为它们是两个不同对象,地址不同。

    new String("hello") == new String("hello")

    比较的是两个对象地址,所以是 false。

    5. 重写 equals 必须重写 hashCode 吗?

    语法上不是必须,但是强烈建议一起重写。

    否则在 HashMap、HashSet 这类集合中可能出现问题。

    十二、总结

    这一篇主要学习了 == 和 equals 的区别。

    == 是运算符。对于基本数据类型,它比较的是具体值;对于引用数据类型,它比较的是对象地址。

    equals 是 Object 类中的方法,默认也是比较地址。但是像 String、Integer 这些类重写了 equals,所以它们可以用来比较内容。

    面试时不要只背一句“== 比较地址,equals 比较内容”,这句话并不完整。更准确的说法应该区分基本数据类型、引用数据类型,以及类有没有重写 equals。

    下一篇我们继续学习 Java 中非常经典的问题:String 为什么是不可变的。

    赞(0)
    未经允许不得转载:171主机测评 » 【Java八股|第1篇】== 和 equals 的区别
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址