本文目录导读:

这是一个非常经典的 Java 面试题,也是初学者容易混淆的核心概念,用一个具体的案例来解释,确实是最直观的方式。
我们先直接看案例,然后再总结原理。
案例代码
public class EqualsAndDoubleEqual {
public static void main(String[] args) {
// ---------- 场景 1: 基本数据类型 ----------
int a = 10;
int b = 10;
System.out.println("基本类型 int 比较: " + (a == b));
// 输出: true (比较值)
// ---------- 场景 2: 引用类型 - 字符串字面量 ----------
String s1 = "hello";
String s2 = "hello";
System.out.println("字符串字面量 == 比较: " + (s1 == s2));
// 输出: true (因为字符串常量池,s1和s2指向同一个对象)
System.out.println("字符串字面量 equals 比较: " + s1.equals(s2));
// 输出: true (比较内容)
// ---------- 场景 3: 引用类型 - new 创建的不同字符串对象 ----------
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println("new String == 比较: " + (s3 == s4));
// 输出: false (s3和s4是堆中两个不同的对象,内存地址不同)
System.out.println("new String equals 比较: " + s3.equals(s4));
// 输出: true (String的equals方法被重写,比较的是字符内容)
// ---------- 场景 4: 引用类型 - 自定义类 (未重写equals) ----------
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
System.out.println("自定义类 == 比较: " + (p1 == p2));
// 输出: false (两个不同对象,地址不同)
System.out.println("自定义类 equals 比较 (未重写): " + p1.equals(p2));
// 输出: false (Object的equals默认也是比较地址,所以为false)
// ---------- 场景 5: 引用类型 - 自定义类 (重写equals) ----------
PersonWithEquals p3 = new PersonWithEquals("李四", 25);
PersonWithEquals p4 = new PersonWithEquals("李四", 25);
System.out.println("重写equals类 == 比较: " + (p3 == p4));
// 输出: false (依然是两个不同对象)
System.out.println("重写equals类 equals 比较: " + p3.equals(p4));
// 输出: true (我们重写了equals,比较的是name和age的内容)
}
}
// 辅助类1:没有重写equals
class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
// 辅助类2:重写了equals
class PersonWithEquals {
String name;
int age;
PersonWithEquals(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 如果指向同一个对象,直接返回true
if (obj == null || getClass() != obj.getClass()) return false;
PersonWithEquals that = (PersonWithEquals) obj;
return age == that.age && name.equals(that.name); // 比较核心属性
}
}
运行结果
基本类型 int 比较: true
字符串字面量 == 比较: true
字符串字面量 equals 比较: true
new String == 比较: false
new String equals 比较: true
自定义类 == 比较: false
自定义类 equals 比较 (未重写): false
重写equals类 == 比较: false
重写equals类 equals 比较: true
| 比较方面 | (双等号) | .equals() |
|---|---|---|
| 适用于基本类型 | ✅ 比较的是数值是否相等 | ❌ 不能用于基本类型 |
| 适用于引用类型 | ✅ 比较的是内存地址 (是否指向同一个对象) | ✅ 默认比较地址,但通常重写后比较内容 |
| String类的默认行为 | 比较地址 (但字面量复用常量池) | ✅ 已重写,比较字符串内容 |
| 是否可自定义 | 不能 (Java语法固定) | 可以 (在自定义类中重写该方法,实现自己的比较逻辑) |
关键图解 (以 new String() 为例)
内存堆:
s3 ------> [ 字符串对象 "hello" ] (地址 0x100)
s4 ------> [ 字符串对象 "hello" ] (地址 0x200)
s3 == s4 => false (因为 0x100 != 0x200,比较的是地址)
s3.equals(s4) => true (因为 String的equals方法会逐字符对比: 'h'='h', 'e'='e', ...)
几点补充说明
- 基本类型没有
equals方法:int a = 10; a.equals(b);这种写法会编译报错。 - String 的 有时为 true:就像案例中的
s1 == s2,这是因为 JVM 的字符串常量池机制,直接用双引号"hello"创建的字符串,JVM 会先在常量池查找,如果已有就直接复用,s1和s2指向的是同一个对象, 返回 true,但new String("hello")一定会创建新对象, 返回 false。 - 重写 equals 必须重写 hashCode:这是 Java 的约定。
p3.equals(p4)返回 true,那么它们的hashCode()也应该相同,否则在HashMap、HashSet等集合中会出现逻辑错误,IDE (如 IDEA, Eclipse) 可以自动生成这两个方法。
简单一句话概括:
- 比的是“是不是同一个人” (地址/值)。
equals(重写后) 比的是“人身上的内容是不是一样” (属性内容)。