- 判断引用是否相等(是否是同一个对象)
- 是否相等(两个对象的状态/值是否相同)
下面我们详细讲解这两种方法,以及它们的区别和最佳实践。

运算符
是 Java 中的一个关系运算符,用于比较两个基本数据类型或两个引用数据类型。
对于基本数据类型 (Primitive Types)
比较的是它们的值是否相等。
int a = 10; int b = 10; System.out.println(a == b); // 输出: true double c = 3.14; double d = 3.14; System.out.println(c == d); // 输出: true
对于引用数据类型 (Reference Types)
比较的是两个引用变量是否指向堆内存中的同一个对象(即它们的内存地址是否相同)。
// 创建两个内容相同的 String 对象
String s1 = new String("hello");
String s2 = new String("hello");
// s1 和 s2 是两个不同的对象,它们在内存中的地址不同
System.out.println(s1 == s2); // 输出: false
// 创建一个引用,指向 s1 指向的对象
String s3 = s1;
// s3 和 s1 指向同一个对象
System.out.println(s1 == s3); // 输出: true
小结:
- 比较基本类型:用 ,比较的是值。
- 比较对象引用:用 ,比较的是内存地址(是否是同一个对象)。
equals() 方法
equals() 是 Object 类中的一个方法。所有 Java 对象都继承了这个方法。
Object 类中的 equals() 方法
查看 Object 类的源码,你会发现它的 equals() 方法实现和 是完全一样的,都是比较两个对象的内存地址。
// Object.java 中的源码
public boolean equals(Object obj) {
return (this == obj);
}
这意味着,如果你直接使用一个自定义类的对象来调用 equals(),它和 没有任何区别。
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
Person p3 = p1;
// p1 和 p2 是两个不同的对象
System.out.println(p1.equals(p2)); // 输出: false (因为 equals 默认行为是 ==)
System.out.println(p1 == p2); // 输出: false
// p1 和 p3 是同一个对象
System.out.println(p1.equals(p3)); // 输出: true
System.out.println(p1 == p3); // 输出: true
}
}
重写 equals() 方法(核心)
为了能够比较两个对象是否相等,我们必须重写(Override) equals() 方法。
重写 equals() 方法的最佳实践(遵循 Java 官方规范):
- 自反性:对于任何非
null的引用值x,x.equals(x)必须返回true。 - 对称性:对于任何非
null的引用值x和y,x.equals(y)必须与y.equals(x)具有相同的返回值。 - 传递性:对于任何非
null的引用值x、y和z,x.equals(y)返回truey.equals(z)返回true,x.equals(z)也必须返回true。 - 一致性:对于任何非
null的引用值x和y,只要对象上equals比较中所用的信息没有被修改,多次调用x.equals(y)就要一致地返回true或false。 - 非空性:对于任何非
null的引用值x,x.equals(null)必须返回false。
重写 equals() 的标准模板:
@Override
public boolean equals(Object o) {
// 1. 检查是否是同一个对象的引用
if (this == o) {
return true;
}
// 2. 检查参数是否为 null,或者是否是同一个类
// 使用 getClass() 可以确保类型完全一致,子类不会和父类相等
// 如果希望子类和父类的实例可以相等(如果它们内容相等),则用 instanceof
if (o == null || getClass() != o.getClass()) {
return false;
}
// 3. 将对象进行类型转换
Person person = (Person) o;
// 4. 比较类的所有关键字段
// 先比较基本类型或不可变对象(如 String)
// 使用 Objects.equals 可以避免空指针异常
if (age != person.age) {
return false;
}
// 使用 Objects.equals 来比较 String,可以处理 null 值的情况
return Objects.equals(name, person.name);
}
一个完整的例子:
import java.util.Objects;
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 重写 equals 方法
@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. 比较字段
return age == person.age &&
Objects.equals(name, person.name);
}
// 建议同时重写 hashCode(),否则在集合中会出现问题
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
Person p3 = new Person("Bob", 30);
System.out.println(p1.equals(p2)); // 输出: true (内容相等)
System.out.println(p1 == p2); // 输出: false (引用不同)
System.out.println(p1.equals(p3)); // 输出: false (内容不等)
}
}
hashCode() 方法
这是一个与 equals() 紧密相关的重要方法。
黄金法则:
如果两个对象根据
equals()方法是相等的,那么调用这两个对象的hashCode()方法必须产生相同的整数结果。
为什么需要这个规则?
这个规则主要是为了在 Java 集合框架(特别是 HashMap, HashSet, Hashtable)中正确地工作,这些集合使用哈希码来确定对象应该存储在哪个“桶”(bucket)中。
- 当你向
HashSet中添加一个对象时,集合会先计算该对象的hashCode()来找到它可能存在的位置。 - 如果发现该位置已经有对象了(哈希冲突),集合就会调用
equals()方法来比较这两个对象是否真的相等。equals()返回true,说明是重复元素,不会被添加。equals()返回false,说明是不同的对象,但恰好哈希码相同,它们会以链表或红黑树的形式存放在同一个桶中。
如果你只重写了 equals() 而没有重写 hashCode(),会发生什么?相同的对象(equals() 返回 true)可能会有不同的 hashCode(),当它们被放入 HashSet 时,会被计算到不同的桶中,导致 HashSet 中出现两个“内容相同”的元素,这破坏了集合的唯一性。
只要你重写了 equals(),就必须同时重写 hashCode()。
如何重写 hashCode()?
现代 Java(Java 7+)推荐使用 Objects.hash() 工具方法,它非常方便且高效。
// 好的重写方式
@Override
public int hashCode() {
return Objects.hash(name, age);
}
// 手动重写(效果一样)
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age; // 31 是一个常用的质数,可以减少哈希冲突
return result;
}
总结与对比
| 特性 | 运算符 | equals() 方法 |
|---|---|---|
| 作用 | 比较左右两边的值 | 比较两个对象的内容是否逻辑相等 |
| 基本类型 | 比较值 | 不可用,会编译错误 |
| 引用类型 | 比较内存地址(是否是同一个对象) | Object 类中默认比较内存地址,需要重写才能比较内容 |
| 如何使用 | a == b |
a.equals(b) |
| 何时使用 | - 确认两个引用变量指向的是否是同一个实例。 - 比较基本类型。 |
- 判断两个对象的状态/值是否相等(判断两个 Person 对象是否代表同一个人)。 |
| 重要提醒 | - | 重写 equals() 必须同时重写 hashCode(),否则在集合中会出问题。 |
特殊情况:String 类
String 类是一个非常经典的例子,它已经帮我们重写了 equals() 和 hashCode() 方法。
// String 类已经重写了 equals(),比较的是字符串内容
String s1 = "hello"; // 字符串字面量会被放入字符串常量池
String s2 = "hello";
System.out.println(s1 == s2); // 输出: true (因为 s1 和 s2 引用了常量池中的同一个对象)
System.out.println(s1.equals(s2)); // 输出: true (内容相等)
String s3 = new String("hello");
String s4 = new String("hello");
System.out.println(s3 == s4); // 输出: false (new 创建了两个不同的对象)
System.out.println(s3.equals(s4)); // 输出: true (String 的 equals 比较的是内容)
最终建议
- 判断对象内容是否相等:始终使用
equals()方法。 - 编写自定义类时:
- 如果你需要判断对象内容是否相等(作为
HashMap的key或HashSet的元素),必须同时重写equals()和hashCode()。 - 使用
IDE(如 IntelliJ IDEA 或 Eclipse)可以一键生成这两个方法的正确实现,非常方便和安全。
- 如果你需要判断对象内容是否相等(作为
