- :是一个运算符,用于比较两个变量(引用)的内存地址是否相同,如果地址相同,就说明它们指向的是同一个对象。
equals():是一个方法,用于比较两个对象的内容(值)是否相等,它的默认行为(在Object类中)和 一样,但很多类(如String,Integer等)都重写(Override)了这个方法,以实现自定义的“内容相等”逻辑。
下面我们通过详细的解释、代码示例和对比表格来彻底搞懂它们。

运算符
运算符根据操作数的类型有不同的行为:
a. 比较基本数据类型 (Primitive Types)
当 用于比较 int, double, char, boolean 等基本数据类型时,它比较的是变量的值是否相等。
int a = 10; int b = 10; int c = 20; System.out.println(a == b); // 输出: true,因为 a 和 b 的值都是 10 System.out.println(a == c); // 输出: false,因为 a 的值是 10,c 的值是 20
b. 比较引用数据类型 (Reference Types)
当 用于比较对象(即引用数据类型)时,它比较的是两个引用变量是否指向内存中的同一个对象实例,也就是说,它比较的是对象的内存地址。
// 创建两个 String 对象
String str1 = new String("hello");
String str2 = new String("hello");
// str1 和 str2 是两个不同的对象,它们在内存中有不同的地址
System.out.println(str1 == str2); // 输出: false
// 创建一个引用,让它指向 str1 指向的对象
String str3 = str1;
// str1 和 str3 指向的是同一个对象
System.out.println(str1 == str3); // 输出: true
equals() 方法
equals() 方法是定义在 java.lang.Object 类中的方法,这意味着所有的 Java 对象都继承了这个方法。

a. Object 类中的 equals() 方法
在 Object 类中,equals() 方法的实现和 运算符完全一样,也是比较两个对象的内存地址。
public boolean equals(Object obj) {
return (this == obj);
}
如果你不重写 equals() 方法,那么对于任何自定义的类,equals() 的行为都和 一致。
class Person {
String name;
public Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("张三");
Person p2 = new Person("张三");
Person p3 = p1;
// equals() 没有被重写,所以比较的是内存地址
System.out.println(p1.equals(p2)); // 输出: false,p1 和 p2 是不同对象
System.out.println(p1.equals(p3)); // 输出: true,p1 和 p3 是同一个对象
System.out.println(p1 == p2); // 输出: false
System.out.println(p1 == p3); // 输出: true
}
}
b. 重写 equals() 方法
equals() 方法的真正威力在于重写(Override),当我们希望一个类的对象在“内容”上相等时,就应该重写 equals() 方法。
最经典的例子就是 String 类。
String 类重写了 equals() 方法,使其比较的是字符串的字符序列内容是否相同,而不是内存地址。
String s1 = new String("hello");
String s2 = new String("hello");
// s1 和 s2 是不同的对象,地址不同
System.out.println(s1 == s2); // 输出: false
// 但是它们的内容相同,String 重写了 equals() 方法
System.out.println(s1.equals(s2)); // 输出: true
另一个常见的例子是 java.awt.Point 类,它比较的是 x 和 y 坐标是否相同。
import java.awt.Point; Point p1 = new Point(1, 2); Point p2 = new Point(1, 2); System.out.println(p1 == p2); // 输出: false,不同对象 System.out.println(p1.equals(p2)); // 输出: true,x和y坐标都相同
最佳实践:如何正确重写 equals()?
如果你要创建一个类,并且希望它的对象可以根据业务逻辑来判断“相等”,那么你必须重写 equals() 方法,一个好的实践是重写 hashCode() 方法。
遵循以下步骤可以编写一个健壮的 equals() 方法:
- 检查是否是同一个引用:使用 检查,如果相同,直接返回
true。 - 检查是否为
null:如果另一个对象是null,返回false。 - 检查类型是否相同:使用
instanceof检查,确保两个对象是同一类型。 - 向下转型并比较属性:将对象转型为当前类型,然后逐个比较关键属性是否相等。
- 重写
hashCode():这是非常重要的约定,如果两个对象通过equals()比较是相等的,那么它们的hashCode()必须返回相同的整数值,反之不成立(hashCode相同的对象不一定equals)。
示例:重写 Person 类的 equals()
class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 equals() 方法
@Override
public boolean equals(Object obj) {
// 1. 检查是否是同一个引用
if (this == obj) {
return true;
}
// 2. 检查是否为 null 或类型不同
if (obj == null || getClass() != obj.getClass()) {
return false;
}
// 3. 向下转型
Person other = (Person) obj;
// 4. 比较属性
// 使用 Objects.equals 可以避免 NullPointerException
return this.age == other.age && java.util.Objects.equals(this.name, other.name);
}
// 重写 hashCode() 方法,与 equals() 保持一致
@Override
public int hashCode() {
return java.util.Objects.hash(name, age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);
Person p3 = new Person("李四", 25);
System.out.println(p1.equals(p2)); // 输出: true,因为姓名和年龄都相同
System.out.println(p1.equals(p3)); // 输出: false,因为姓名不同
}
}
总结与对比表格
| 特性 | 运算符 | equals() 方法 |
|---|---|---|
| 本质 | 一个运算符 | 一个方法 |
| 基本类型:比较值是否相等。 引用类型:比较内存地址是否相同。 |
默认(Object类)比较内存地址,通常被重写以比较(如值、状态等)。 |
|
| 使用场景 | - 比较基本数据类型。 - 判断两个引用是否指向同一个对象。 |
- 当需要判断两个对象在逻辑上是否相等时使用(如比较两个字符串内容是否相同)。 |
| 可重写性 | 不可重写。 | 可以被任何子类重写。 |
| 示例 | int a = 5; a == 5; // trueString s1 = "A"; String s2 = "A"; s1 == s2; // true (常量池优化) |
"hello".equals("world"); // false"new Integer(10).equals(new Integer(10)); // true |
一个常见的“陷阱”:String 的 和 equals()
// 情况一:使用双引号创建字符串(字符串字面量)
String s1 = "hello";
String s2 = "hello";
// JVM 会在字符串常量池中只创建一个 "hello" 对象,s1 和 s2 都指向它
System.out.println(s1 == s2); // 输出: true
System.out.println(s1.equals(s2)); // 输出: true
// 情况二:使用 new 关键字创建字符串
String s3 = new String("hello");
String s4 = new String("hello");
// new 关键字总是在堆内存中创建新的对象,即使内容相同
System.out.println(s3 == s4); // 输出: false
System.out.println(s3.equals(s4)); // 输出: true (因为 equals 被重写了)
这个陷阱很好地说明了 比较地址,而 equals() 比较内容。
最终建议
- 比较基本数据类型:永远使用 。
- 比较对象:
- 如果你想知道两个引用是不是指向同一个对象实例,使用 。
- 如果你想知道两个对象在逻辑上是否“相等”、状态相同),使用
equals()。
- 对于
String类型:几乎总是应该使用equals()来比较内容,除非你有特殊需求(比如判断字符串是否来自常量池)。
