杰瑞科技汇

Java与equals方法到底有什么区别?

  • :是一个运算符,用于比较两个变量(引用)的内存地址是否相同,如果地址相同,就说明它们指向的是同一个对象。
  • equals():是一个方法,用于比较两个对象的内容(值)是否相等,它的默认行为(在 Object 类中)和 一样,但很多类(如 String, Integer 等)都重写(Override)了这个方法,以实现自定义的“内容相等”逻辑。

下面我们通过详细的解释、代码示例和对比表格来彻底搞懂它们。

Java与equals方法到底有什么区别?-图1
(图片来源网络,侵删)

运算符

运算符根据操作数的类型有不同的行为:

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 对象都继承了这个方法。

Java与equals方法到底有什么区别?-图2
(图片来源网络,侵删)

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 类,它比较的是 xy 坐标是否相同。

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() 方法:

  1. 检查是否是同一个引用:使用 检查,如果相同,直接返回 true
  2. 检查是否为 null:如果另一个对象是 null,返回 false
  3. 检查类型是否相同:使用 instanceof 检查,确保两个对象是同一类型。
  4. 向下转型并比较属性:将对象转型为当前类型,然后逐个比较关键属性是否相等。
  5. 重写 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; // true
String 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() 来比较内容,除非你有特殊需求(比如判断字符串是否来自常量池)。
分享:
扫描分享到社交APP
上一篇
下一篇