- :是一个运算符,用于比较两个变量是否指向内存中的同一个对象(对于引用类型)或者两个变量的值是否相等(对于基本数据类型)。
equals():是一个方法,它被设计用来比较两个对象的内容(值)是否相等,这个方法的行为是可以被重写的。
下面我们通过详细的解释、示例和最佳实践来彻底搞懂它们。

运算符
的行为取决于你比较的是基本数据类型还是引用类型。
a) 比较基本数据类型
当 用于比较 int, double, char, boolean 等基本数据类型时,它比较的是它们的值是否相等。
int a = 10; int b = 10; System.out.println(a == b); // 输出 true,因为值相等 char c1 = 'A'; char c2 = 65; // 'A' 的 ASCII 码就是 65 System.out.println(c1 == c2); // 输出 true,因为值相等
b) 比较引用类型
当 用于比较对象(引用类型)时,它比较的是这两个引用是否指向堆内存中的同一个对象实例,换句话说,它们是否是“同一个东西”。
// 创建两个 String 对象
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // 输出 false
// 解释:s1 和 s2 是两个不同的对象实例,它们在内存中有不同的地址。
// 创建一个引用,指向 s1 指向的对象
String s3 = s1;
System.out.println(s1 == s3); // 输出 true
// 解释:s1 和 s3 指向了内存中同一个 String 对象。
一个常见的特例:字符串常量池

为了优化性能,Java 对字符串有一个特殊的处理机制——字符串常量池。
// 直接使用双引号创建的字符串,JVM 会先在常量池中查找 String s4 = "hello"; String s5 = "hello"; System.out.println(s4 == s5); // 输出 true // 解释:因为 "hello" 是一个常量,JVM 只会在常量池中创建一个 "hello" 对象。 // s4 和 s5 都被指向了这个常量池中的同一个对象。
equals() 方法
equals() 方法在 Object 类中定义,其默认实现与 完全相同,也是比较两个对象的内存地址是否相同。
// Object 类中的 equals() 方法默认实现
public boolean equals(Object obj) {
return (this == obj);
}
绝大多数 Java 类(如 String, Integer, Date, List 等)都重写(Override)了 equals() 方法,使其能够比较对象的内容(值)是否相等,而不是内存地址。
a) String 类的 equals() 方法
String 类重写了 equals() 方法,用来比较两个字符串对象的字符序列是否完全相同。

String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // 输出 false (比较内存地址)
System.out.println(s1.equals(s2)); // 输出 true (比较内容)
String s3 = "hello";
String s4 = "hello";
System.out.println(s3 == s4); // 输出 true (因为常量池)
System.out.println(s3.equals(s4)); // 输出 true (比较内容)
b) 其他常用类的 equals() 方法
Integer,Double等包装类:比较其包装的基本值是否相等。Integer i1 = new Integer(100); Integer i2 = new Integer(100); System.out.println(i1 == i2); // 输出 false (不同对象) System.out.println(i1.equals(i2)); // 输出 true (值相等)
ArrayList,HashMap等集合类:比较集合的元素和顺序是否完全相同。List<String> list1 = new ArrayList<>(Arrays.asList("A", "B")); List<String> list2 = new ArrayList<>(Arrays.asList("A", "B")); System.out.println(list1 == list2); // 输出 false System.out.println(list1.equals(list2)); // 输出 true
核心区别总结表
| 特性 | 运算符 | equals() 方法 |
|---|---|---|
| 本质 | 一个运算符 | 一个方法 |
| 比较对象 | 基本数据类型 / 引用类型 | 只能是对象 |
| 基本类型:比较值是否相等。 引用类型:比较内存地址是否指向同一个对象。 |
默认比较内存地址(来自 Object 类)。通常被重写,用于比较(值)是否相等。 |
|
| 可重写性 | 不可重写 | 可以被重写 |
| 使用场景 | 比较基本类型变量。 判断两个引用是否指向同一个实例(如单例模式检查)。 |
判断两个对象在逻辑上是否“相等”。 当你需要比较对象内容时,永远应该使用 equals()。 |
最佳实践与“陷阱”
陷阱:忘记检查 null
当你调用一个对象的 equals() 方法时,如果这个对象是 null,会抛出 NullPointerException。
String s = null; String t = "hello"; // s.equals(t); // 这行代码会抛出 NullPointerException!
安全写法:
总是将常量或非可能为 null 的对象放在 equals() 的左侧。
// 安全写法 "hello".equals(s); // s 是 null,这里返回 false,不会报错
更现代的写法是使用 Objects.equals() 工具方法(Java 7+),它会自动处理 null 的情况。
import java.util.Objects; // Objects.equals() 内部会处理 null,非常安全 boolean result = Objects.equals(s, t); // s 或 t 有一个为 null,返回 false
陷阱:在需要比较内容时误用
这是最常见的错误,尤其是在比较 String 或 Integer 时。
// 错误示例:比较用户输入的字符串
Scanner scanner = new Scanner(System.in);
System.out.print("请输入密码: ");
String userInput = scanner.nextLine();
String correctPassword = "123456";
if (userInput == correctPassword) { // 错误!比较的是地址,不是内容
System.out.println("密码正确");
} else {
System.out.println("密码错误"); // 几乎总是执行到这里
}
// 正确示例
if (userInput.equals(correctPassword)) { // 正确!比较的是内容
System.out.println("密码正确");
} else {
System.out.println("密码错误");
}
自定义类的 equals() 重写(非常重要)
当你创建自己的类时,如果你希望这个类的对象能够根据其内容进行比较(在 HashSet 或 HashMap 中使用),你就必须正确地重写 equals() 和 hashCode() 方法。
重写 equals() 的黄金法则:
- 自反性:
x.equals(x)必须返回true。 - 对称性:
x.equals(y)和y.equals(x)必须返回相同的值。 - 传递性:
x.equals(y)为true,且y.equals(z)为true,x.equals(z)也必须为true。 - 一致性:只要对象内容没变,多次调用
equals()必须返回相同的结果。 - 非空性:
x.equals(null)必须返回false。
一个简单的 equals() 重写模板:
public class Person {
private String name;
private int age;
// 构造函数、getters 省略...
@Override
public boolean equals(Object o) {
// 1. 检查是否是同一个对象实例
if (this == o) return true;
// 2. 检查参数是否为 null 或类型是否匹配
if (o == null || getClass() != o.getClass()) return false;
// 3. 类型转换
Person person = (Person) o;
// 4. 比较关键字段
// 注意:基本类型用 ==,对象类型用 Objects.equals()
return age == person.age &&
Objects.equals(name, person.name);
}
// 注意:重写了 equals(),就必须重写 hashCode(),否则在集合中会出问题
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
| 情景 | 应该使用 |
|---|---|
比较两个基本数据类型(int, double 等)的值 |
|
| 判断两个引用是否指向同一个对象(内存地址相同) | |
| 判断两个对象在逻辑上是否相等相同) | equals() |
在不确定对象是否为 null 的情况下比较内容 |
Objects.equals() |
记住这个核心原则: 比的是身份(地址),equals() 比的是内容(值),在处理对象时,除非你有明确的理由要判断它们是否是同一个实例,否则都应该使用 equals() 来比较它们的内容。
