杰瑞科技汇

Java如何判断对象是否相等?

  1. 判断引用是否相等(是否是同一个对象)
  2. 是否相等(两个对象的状态/值是否相同)

下面我们详细讲解这两种方法,以及它们的区别和最佳实践。

Java如何判断对象是否相等?-图1
(图片来源网络,侵删)

运算符

是 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 官方规范):

  1. 自反性:对于任何非 null 的引用值 xx.equals(x) 必须返回 true
  2. 对称性:对于任何非 null 的引用值 xyx.equals(y) 必须与 y.equals(x) 具有相同的返回值。
  3. 传递性:对于任何非 null 的引用值 xyzx.equals(y) 返回 true y.equals(z) 返回 truex.equals(z) 也必须返回 true
  4. 一致性:对于任何非 null 的引用值 xy,只要对象上 equals 比较中所用的信息没有被修改,多次调用 x.equals(y) 就要一致地返回 truefalse
  5. 非空性:对于任何非 null 的引用值 xx.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 比较的是内容)

最终建议

  1. 判断对象内容是否相等:始终使用 equals() 方法。
  2. 编写自定义类时
    • 如果你需要判断对象内容是否相等(作为 HashMapkeyHashSet 的元素),必须同时重写 equals()hashCode()
    • 使用 IDE(如 IntelliJ IDEA 或 Eclipse)可以一键生成这两个方法的正确实现,非常方便和安全。
分享:
扫描分享到社交APP
上一篇
下一篇