杰瑞科技汇

Java中equals()与==到底有啥区别?

一句话概括:

  • 是一个运算符,用于比较两个变量是否相等,对于基本数据类型,比较的是值本身;对于引用数据类型,比较的是它们所指向对象的内存地址(即是否是同一个对象)。
  • equals() 是一个方法,用于比较两个对象的(逻辑上)是否相等,它的默认行为(继承自 Object 类)与 相同,即比较内存地址,但很多类(如 String, Integer)都重写了此方法,以实现自定义的“内容相等”逻辑。

详细对比

特性 (运算符) equals() (方法)
本质 Java语言提供的二元运算符 Object 类中定义的实例方法
比较对象 - 基本数据类型 ( int, double 等)
- 引用数据类型 (任何对象)
只能比较对象,如果传入基本数据类型,会先进行自动装箱。
- 基本类型:比较变量的是否相等。
- 引用类型:比较两个变量引用的内存地址是否相同(即是否指向同一个对象实例)。
默认情况下(Object.equals()),与 相同,比较内存地址。
但可以被重写,以比较对象的或状态是否逻辑上相等。
能否被重写 不能。 是Java语言固有的运算符,其行为是固定的。 可以,任何类都可以重写 equals() 方法来定义自己的“相等”规则。

代码示例与分步解析

基本数据类型

对于基本数据类型, 比较的是它们的值。

int a = 10;
int b = 10;
int c = 20;
System.out.println(a == b); // 输出: true (值都是10)
System.out.println(a == c); // 输出: false (值不同)
// 对于基本类型,调用 equals() 是不允许的,编译会报错
// a.equals(b); // Compile Error: int cannot be dereferenced

基本数据类型, 比较值,equals() 不适用。


引用数据类型(未重写 equals() 的情况)

当我们创建一个自定义类时,如果没有重写 equals() 方法,那么它使用的是 Object 类中的默认实现,这个实现和 是完全一样的——比较内存地址。

class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
    // 注意:我们没有重写 equals() 方法
}
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("张三");
        Person p2 = new Person("张三");
        Person p3 = p1;
        System.out.println("p1 == p2 ? " + (p1 == p2)); // 输出: false
        // p1 和 p2 是两个不同的对象,内存地址不同
        System.out.println("p1.equals(p2) ? " + (p1.equals(p2))); // 输出: false
        // equals() 默认行为,比较内存地址,与 == 相同
        System.out.println("p1 == p3 ? " + (p1 == p3)); // 输出: true
        // p3 指向 p1 的内存地址,是同一个对象
        System.out.println("p1.equals(p3) ? " + (p1.equals(p3))); // 输出: true
        // equals() 比较内存地址,与 == 相同
    }
}

对于未重写 equals() 的自定义类, 和 equals() 的行为完全一致,都用于判断两个引用是否指向同一个对象。


引用数据类型(已重写 equals() 的情况)

很多Java核心类库中的类都重写了 equals() 方法,使其能够比较对象的内容。

经典案例:String

String 类重写了 equals() 方法,用来比较两个字符串对象的内容(字符序列)是否相同,而不管它们的内存地址是否相同。

String s1 = new String("hello");
String s2 = new String("hello");
String s3 = s1;
System.out.println("s1 == s2 ? " + (s1 == s2)); // 输出: false
// s1 和 s2 是两个不同的对象,内存地址不同
System.out.println("s1.equals(s2) ? " + (s1.equals(s2))); // 输出: true
// String 重写了 equals(),比较的是字符串内容 "hello" 是否相同
System.out.println("s1 == s3 ? " + (s1 == s3)); // 输出: true
// s3 指向 s1 的内存地址
System.out.println("s1.equals(s3) ? " + (s1.equals(s3))); // 输出: true相同,地址也相同

另一个案例:Integer 包装类

Integer i1 = new Integer(100);
Integer i2 = new Integer(100);
Integer i3 = 100; // 使用了缓存机制 (JDK 1.7+)
Integer i4 = 100;
Integer i5 = 200;
Integer i6 = 200;
System.out.println("i1 == i2 ? " + (i1 == i2)); // 输出: false (new 创建的对象地址不同)
System.out.println("i1.equals(i2) ? " + (i1.equals(i2))); // 输出: true (值相同)
System.out.println("i3 == i4 ? " + (i3 == i4)); // 输出: true (值在-128到127之间,会使用缓存,是同一个对象)
System.out.println("i3.equals(i4) ? " + (i3.equals(i4))); // 输出: true
System.out.println("i5 == i6 ? " + (i5 == i6)); // 输出: false (值超出缓存范围,是new的对象)
System.out.println("i5.equals(i6) ? " + (i5.equals(i6))); // 输出: true (值相同)

重写 equals() 的最佳实践

如果你想让你的自定义类支持基于内容的比较(在 HashSet, HashMapList.contains() 中使用),你就必须重写 equals() 方法。

根据《Effective Java》的指导,一个好的 equals() 方法应该遵循以下规则,并重写 hashCode() 方法:

  1. 自反性x.equals(x) 必须返回 true
  2. 对称性x.equals(y)y.equals(x) 必须返回相同的值。
  3. 传递性x.equals(y)true,且 y.equals(z)truex.equals(z) 也必须为 true
  4. 一致性:只要对象内容没有被修改,多次调用 x.equals(y) 应该返回相同的值。
  5. 非空性x.equals(null) 必须返回 false

示例:重写 Person 类的 equals()

import java.util.Objects;
class Person {
    private String name;
    private int age;
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // IDE可以一键生成,这里手动展示
    @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. 比较关键字段(使用Objects.equals可以避免NullPointerException)
        return age == person.age && Objects.equals(name, person.name);
    }
    // 重写equals()后,必须重写hashCode(),以保证“相等的对象必须有相同的哈希码”
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("张三", 30);
        Person p2 = new Person("张三", 30);
        Person p3 = new Person("李四", 30);
        System.out.println("p1.equals(p2) ? " + (p1.equals(p2))); // 输出: true (name和age都相同)
        System.out.println("p1.equals(p3) ? " + (p1.equals(p3))); // 输出: false (name不同)
    }
}

总结与何时使用

场景 使用 使用 equals()
比较基本数据类型 总是使用 不适用
判断两个引用是否指向同一个对象 使用 也可以使用,但 更直观、性能更高
比较两个对象的内容是否逻辑上相等 不使用(除非你想判断是否是同一个对象) 必须使用,前提是相关的类已经重写了此方法(如 String, Date 等)
在集合框架(Set, Map)中 HashMapputget 方法中, 用于判断键的引用是否相同。 HashSetadd 方法或 Listcontains 方法中,equals() (或 hashCode) 用于判断元素是否已存在。

黄金法则

  • 如果你想知道两个变量是否指向内存中的同一个东西,用 。
  • 如果你想知道两个对象在逻辑上是否“相等”(两个字符串是否包含相同的字符序列),用 equals()
分享:
扫描分享到社交APP
上一篇
下一篇