Java equals() 方法(深入浅出)

Java equals() 方法详解:从基础用法到深层原理

在 Java 编程中,判断两个对象是否“相等”是一个非常常见的需求。你可能已经用过 == 操作符来比较两个变量,但当你尝试比较两个对象时,往往会发现结果与预期不符。这时候,你真正需要的,是 equals() 方法。

Java equals() 方法 不仅是 Java 语言的核心组成部分,也是面向对象编程中“相等性”判断的基础。如果你在开发中遇到对象比较结果异常,那么很可能是你对 equals() 的理解还不够深入。本文将带你从零开始,逐步掌握 equals() 的使用、重写规则以及常见陷阱。


为什么不能只用 == 来比较对象?

在 Java 中,== 操作符用于比较两个变量的。但对于引用类型(如对象),它比较的是两个变量是否指向内存中的同一个对象,而不是它们的内容是否相同。

举个例子:

String str1 = new String("hello");
String str2 = new String("hello");

System.out.println(str1 == str2); // 输出 false

尽管 str1str2 的内容都是 "hello",但它们是两个不同的对象,分别在堆内存中创建。因此 == 返回 false

这时,我们就需要 equals() 方法来比较对象的内容,而不是引用地址。


equals() 方法的默认行为

Object 类是 Java 中所有类的父类。equals() 方法就定义在 Object 类中。它的默认实现是:

public boolean equals(Object obj) {
    return (this == obj);
}

也就是说,如果不重写 equals(),那么它等价于 ==,只比较引用是否相同。

我们来看一个例子:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 没有重写 equals() 方法
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 25);
        Person p2 = new Person("张三", 25);

        System.out.println(p1.equals(p2)); // 输出 false
    }
}

虽然两个 Person 对象的属性完全一样,但由于没有重写 equals(),系统默认使用 Object 的实现,即比较引用,因此结果是 false

这就说明:如果你希望两个对象在内容相同时返回 true,就必须重写 equals() 方法。


如何正确重写 equals() 方法?

重写 equals() 方法需要遵循一定的规范,否则可能会导致逻辑错误或违反 Java 的契约。以下是重写时必须遵守的五个原则(也叫“契约”):

  1. 自反性x.equals(x) 必须返回 true
  2. 对称性:如果 x.equals(y) 返回 true,那么 y.equals(x) 也必须返回 true
  3. 传递性:如果 x.equals(y)y.equals(z) 都返回 true,那么 x.equals(z) 也必须返回 true
  4. 一致性:多次调用 equals(),只要对象信息没有改变,结果应保持一致。
  5. 与 null 的比较x.equals(null) 应该返回 false

下面是一个正确重写的 Person 类示例:

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        // 1. 如果两个对象引用相同,直接返回 true
        if (this == obj) return true;

        // 2. 如果传入的对象为 null,返回 false
        if (obj == null) return false;

        // 3. 如果类型不同,返回 false
        if (getClass() != obj.getClass()) return false;

        // 4. 类型相同,强制转换为 Person 类型
        Person other = (Person) obj;

        // 5. 逐个比较关键属性
        return age == other.age && (name == null ? other.name == null : name.equals(other.name));
    }

    // 重写 hashCode() 方法以保持一致性(重要!)
    @Override
    public int hashCode() {
        int result = name != null ? name.hashCode() : 0;
        result = 31 * result + age;
        return result;
    }
}

💡 提示:重写 equals() 时,必须同时重写 hashCode()。如果只重写 equals() 而不重写 hashCode(),在使用 HashMapHashSet 等集合时会出现不可预测的行为。


常见的 equals() 使用场景

1. 字符串比较(String)

Java 中的 String 类已经正确重写了 equals() 方法,所以你可以安全地使用它来比较字符串内容:

String s1 = "hello";
String s2 = new String("hello");

System.out.println(s1.equals(s2)); // 输出 true

⚠️ 注意:不要用 == 比较字符串内容,除非你明确知道它们是同一个实例(如常量池中的字符串)。

2. 包装类比较(Integer、Double 等)

IntegerDouble 等包装类也重写了 equals() 方法,用于比较值的大小:

Integer a = 100;
Integer b = 100;

System.out.println(a.equals(b)); // 输出 true

// 但注意:对于大数值,会创建新对象
Integer c = 200;
Integer d = 200;

System.out.println(c.equals(d)); // 输出 true

虽然 == 在某些情况下(如 -128 到 127)会返回 true(因为缓存机制),但为了安全,建议始终使用 equals()


equals() 与 == 的对比表

比较方式 比较内容 适用类型 是否推荐
== 引用地址 所有类型 基本类型推荐,对象类型慎用
equals() 对象内容 引用类型 对象比较必须使用

最佳实践:对于对象比较,永远使用 equals(),除非你明确要比较引用。


重写 equals() 的常见陷阱

陷阱一:忽略 null 值

// ❌ 错误写法
public boolean equals(Object obj) {
    Person other = (Person) obj;
    return age == other.age && name.equals(other.name);
}

如果 objnull,这行代码会抛出 NullPointerException

陷阱二:没有检查类型

// ❌ 错误写法
if (obj instanceof Person) { ... }

虽然 instanceof 看似安全,但 equals() 契约要求必须保证对称性。如果 PersonStudent 类都重写了 equals(),但未统一使用 getClass(),可能破坏对称性。

陷阱三:只比较部分字段

如果 Personnameageaddress,但只比较 nameage,那么两个 address 不同的对象也会被认为相等,这不符合业务逻辑。


实际项目中的最佳实践

在真实项目中,我们通常会使用 IDE(如 IntelliJ IDEA、Eclipse)来自动生成 equals()hashCode() 方法。例如在 IntelliJ 中:

  1. 右键点击类名 → Generate → equals() and hashCode()
  2. 选择需要比较的字段
  3. 生成代码

但理解背后的原理仍然非常重要。尤其是在调试集合类(如 HashSet)时,如果 equals()hashCode() 有问题,会导致元素无法正确去重或查找失败。


总结

Java equals() 方法 是判断两个对象内容是否相等的核心机制。它与 == 有着本质区别:== 比较引用,equals() 比较内容。

  • 对于基本类型,使用 ==
  • 对于对象,使用 equals()
  • 重写 equals() 时,必须同时重写 hashCode()
  • 遵循五个契约:自反性、对称性、传递性、一致性、与 null 的比较;
  • 使用 IDE 生成代码可以提高效率,但理解原理才能避免陷阱。

掌握 equals() 方法,不仅能让你写出更安全的代码,也能在面试中脱颖而出。希望这篇文章能帮你彻底搞懂这个看似简单、实则深奥的 Java 特性。