← 返回题目列表

== 和 equals() 有什么区别?

高频 简单 第 1 / 32 题 更新于 2026/07/25
Java 基础equals引用比较

简化版

== 比的是值本身:基本类型比数值,引用类型比的是地址(是不是同一个对象)。equals() 是个方法,默认也比地址,但类通常会重写它来比业务内容是否相等(比如两个 String 字符相同就算相等)。所以简单记:== 比地址,equals() 比内容(前提是被重写了)。

详细版

== 分两种情况

  • 用在基本类型intdoublechar…)上:比较数值1 == 1 为 true。
  • 用在引用类型上:比较引用地址——两个变量是不是指向堆里同一个对象。

equals()Object 的方法

public boolean equals(Object obj) {
    return this == obj;   // Object 默认实现就是 ==,比地址
}

所以如果一个类没重写 equals(),它的 equals== 效果一样(都比地址)。但 StringInteger 等都重写了 equals() 去比内容

String a = new String("hi");
String b = new String("hi");
System.out.println(a == b);        // false,两个不同对象,地址不同
System.out.println(a.equals(b));   // true,String 重写了 equals 比字符内容

完整版教学

一、为什么引用类型 == 比的是地址

Java 变量对引用类型存的是对象在堆中的地址(引用),不是对象本身。所以 a == b 问的是「a 和 b 这两个引用是不是指向同一块内存」,而不是「它们的内容一样吗」。

new String("hi") 每次都在堆上新建一个对象,两次 new 得到两个不同地址,== 自然 false。而它们的字符内容相同,重写过的 equals() 才返回 true。这就是「== 看是不是同一个,equals 看内容一不一样」。

二、String 的 == 陷阱:字面量 vs new

String a = "hi";          // 字面量,进字符串常量池
String b = "hi";          // 复用池中同一个对象
System.out.println(a == b);        // true!指向常量池同一对象

String c = new String("hi");       // new 强制在堆上建新对象
System.out.println(a == c);        // false,地址不同
System.out.println(a.equals(c));   // true,内容相同

字面量 "hi" 会进字符串常量池、相同内容复用一份,所以 a == b 是 true;而 new String() 一定新建对象,所以 a == c 是 false。这是面试最爱考的坑——比字符串内容永远用 equals(),别用 ==

三、包装类的 == 陷阱:Integer 缓存

Integer x = 127, y = 127;
System.out.println(x == y);   // true

Integer m = 128, n = 128;
System.out.println(m == n);   // false!

Integer 有个 -128~127 的缓存池,这个范围内的值 Integer.valueOf() 会复用缓存对象,所以 127 == 127 为 true;超出范围(128)就 new 新对象,== 变 false。所以包装类比值也必须用 equals()(详见「自动装箱拆箱」那道题)。

⚠️ 一条铁律:只要是引用类型(对象),比内容就用 equals(),别用 ==== 只在基本类型、或确实要判断「是不是同一个对象」时才用。

四、equals 和 hashCode 的连带关系

重写 equals() 时几乎总要一起重写 hashCode(),否则对象放进 HashMap/HashSet 会出问题——两个 equals 相等的对象若 hashCode 不同,会被分到不同的桶,导致「放进去却找不到」。这部分详见「equals() 和 hashCode() 的关系」那道题。

五、null、安全比较与数组边界

调用实例方法需要接收者非空,因此 a.equals(b)a == null 时会抛 NPE。Objects.equals(a, b) 把这套分支统一封装:两个都为空返回 true,只有一个为空返回 false,两个都非空才调用 a.equals(b)。这比在业务代码里散落判空更稳妥。

Objects.equals(null, null);          // true
Objects.equals(null, "x");          // false
Objects.equals(new int[]{1}, new int[]{1}); // false,数组未按元素重写 equals
Arrays.equals(new int[]{1}, new int[]{1});  // true,逐元素比较
数据类型内容比较方式说明
基本类型==直接比较数值;浮点数还要考虑精度
普通对象Objects.equals(a, b)null 安全,实际语义由类型的 equals 决定
一维数组Arrays.equals(a, b)逐元素比较
嵌套对象数组Arrays.deepEquals(a, b)递归比较嵌套数组
BigDecimal视业务选 equalscompareToequals 还比较 scale,如 1.0 与 1.00 不等

这说明“对象比内容用 equals”只是起点,还必须确认具体类型如何定义内容相等。业务判等从来不是由操作符自动替你决定的。

六、常见误区与追问

  • 误区:用 == 比较两个 String 内容。 常量池可能让错误写法偶尔返回 true,正确方式是内容比较。
  • 误区:用 == 比较包装类数值。 缓存和拆箱会改变表象,应避免依赖对象身份。
  • 误区:认为所有类的 equals() 都比较内容。 未重写时,Object.equals() 仍然比较同一引用。
  • 追问:两个数组为何 equals() 为 false? 数组继承了 Object 的身份判等,应使用 Arrays.equalsdeepEquals
  • 追问:BigDecimal("1.0").equals(new BigDecimal("1.00")) 为何是 false? 它同时比较数值和 scale;忽略刻度时可用 compareTo() == 0
  • 追问:枚举为什么常用 == 每个枚举常量在 JVM 中是唯一实例,身份比较明确且 null 安全。

易错点:先问“我要比较身份还是业务值”,再问“这个类型怎样定义业务值”,最后才选择 API。

浮点数还有一条独立边界:计算结果常有舍入误差,0.1 + 0.2 == 0.3 通常为 false。此时问题不是引用身份,也不是 equals 是否重写,而是二进制浮点无法精确表示部分十进制小数。

double actual = 0.1 + 0.2;
boolean close = Math.abs(actual - 0.3) < 1e-9; // 按业务误差比较

金额等必须精确表达十进制的场景应使用 BigDecimal,并明确是否把 scale 纳入相等语义;科学计算则通常选择领域允许的误差阈值。

七、加强记忆

== 是语言操作符:基本类型比较数值,引用类型比较是否指向同一对象;equals() 是可重写的业务判等协议,默认实现仍等同身份比较。日常对象比较优先用 null 安全的 Objects.equals,数组使用 Arrays 工具,特殊数值类型还要理解其契约。常量池和包装缓存只会影响身份,不能拿来推断内容相等。