Android面试-Java 中 == 和 equals 的区别:从内存模型到实战避坑

​

 在 Java 面试和日常开发中,== 和 equals 的区别是一个绕不开的基础问题。很多人能背出“== 比较地址,equals 比较内容”,但真正理解背后的内存模型、重写规则和常见坑点的人并不多。本文从原理出发,结合代码示例,把这个问题讲透。

一、一句话结论

== 比较的是“值”本身:对于基本类型比较数值,对于引用类型比较对象的内存地址(引用是否指向同一个对象)。
equals 是 Object 类的方法,默认实现等价于 ==,但很多类(如 String、Integer)重写了它,用于比较对象的“内容”是否相等。

二、== 的本质:比较值或引用地址

== 是 Java 中的关系运算符,它的行为取决于操作数的类型。

2.1 基本数据类型:比较数值

对于 byte、short、int、long、float、double、char、boolean 这八种基本类型,== 直接比较它们的值。

int a = 10;
int b = 10;
System.out.println(a == b); // true,数值相等

char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2); // true,'A' 的 ASCII 码就是 65

2.2 引用数据类型:比较内存地址

对于对象引用,== 比较的是两个引用是否指向堆内存中的同一个对象。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2);      // false,两个不同的对象
System.out.println(s1.equals(s2)); // true,内容相同

上面这段代码中,s1 和 s2 分别通过 new 在堆上创建了两个独立对象,虽然内容都是 "hello",但地址不同,所以 == 返回 false。

三、equals 的本质:可被重写的内容比较

equals 定义在 java.lang.Object 中,源码如下:

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

可以看到,Object 的默认实现就是 ==。也就是说,如果一个类没有重写 equals,那么它的 equals 和 == 效果完全一样。

3.1 String 对 equals 的重写

String 重写了 equals,逐字符比较内容:

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    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;
}

注意第一行 if (this == anObject):先比较地址,地址相同直接返回 true,这是一种性能优化。

3.2 包装类对 equals 的重写

Integer、Long 等包装类也重写了 equals,比较的是包装的数值:

Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
System.out.println(i1 == i2);      // false,不同对象
System.out.println(i1.equals(i2)); // true,数值相同

四、经典陷阱:Integer 缓存机制

这是面试中出现频率极高的一个坑:

Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true

Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false

原因在于 Integer 内部维护了一个缓存数组 IntegerCache,默认缓存范围是 -128 到 127。当使用自动装箱(Integer.valueOf(int))时,如果数值在缓存范围内,会直接返回缓存中的同一个对象,因此 == 为 true;超出范围则会 new 一个新对象,== 为 false。

结论:比较包装类的值,永远使用 equals,不要用 ==。

五、String 常量池的影响

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

System.out.println(s1 == s2);      // true,都指向常量池中的同一个对象
System.out.println(s1 == s3);      // false,s3 是堆上的新对象
System.out.println(s1.equals(s3)); // true,内容相同

字面量 "hello" 会被放入字符串常量池,s1 和 s2 指向同一个常量池对象;而 new String("hello") 会在堆上创建新对象。

六、如何正确重写 equals

自定义类如果需要按内容比较,必须重写 equals,同时强烈建议重写 hashCode。

import java.util.Objects;

public class User {
    private Long id;
    private String name;

    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return Objects.equals(id, user.id) && Objects.equals(name, user.name);
    }

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

重写 equals 需要满足五个约定:自反性、对称性、传递性、一致性,以及与 null 比较返回 false。

6.1 为什么必须同时重写 hashCode

Java 规范要求:如果两个对象 equals 返回 true,那么它们的 hashCode 必须相等。 否则在 HashMap、HashSet 等基于哈希的集合中会出现“存进去却取不出来”的问题。

Set<User> set = new HashSet<>();
set.add(new User(1L, "Tom"));
System.out.println(set.contains(new User(1L, "Tom"))); // 重写 hashCode 后为 true

七、对比总结

对比项==equals
类型 运算符 Object 类的方法
基本类型 比较数值 不适用(基本类型无方法)
引用类型 比较内存地址 默认比较地址,重写后比较内容
是否可重写 否 是
执行效率 高 相对较低(可能逐字符/逐字段比较)
典型使用场景 基本类型比较、判断是否为同一对象、判空 字符串、包装类、自定义对象的逻辑相等判断

八、实战建议

  • 基本类型:直接用 ==。
  • 字符串:内容比较用 equals,且把常量放在前面,如 "abc".equals(str),避免空指针。
  • 包装类:一律用 equals,警惕 Integer 缓存范围。
  • 自定义对象:按业务主键重写 equals 和 hashCode,推荐使用 Objects.equals 和 Objects.hash。
  • 枚举:可以直接用 ==,因为枚举实例全局唯一。
  • 判空:用 == null,不要用 equals。

九、小结

== 关注“是不是同一个”,equals 关注“内容是不是一样”。理解这一点,再结合基本类型与引用类型的差异、String 常量池、Integer 缓存以及 equals/hashCode 契约,就能在实际开发中避开绝大多数相关坑点。

使用小程序,查看更多面试题目,随时随地刷题,刷题更方便!

code2

 

​
posted @ 2026-11-13 19:55  自由侠  阅读(4)  评论(0)    收藏  举报