杰瑞科技汇

Java中=与equals有何区别?

核心总结(先说结论)

  • Java中=与equals有何区别?-图1
    (图片来源网络,侵删)
    • 基本数据类型:比较的是 是否相等。
    • 引用数据类型:比较的是 内存地址 是否相等,即判断两个引用是否指向同一个对象。
  • equals()

    • 它是一个方法,不是运算符。
    • 默认行为:如果没有被重写,equals() 的作用和 一样,也是比较两个对象的内存地址。
    • 重写后:我们通常在自定义类中重写 equals() 方法,使其比较对象的 内容(属性值) 是否相等。

运算符

是 Java 中的一个关系运算符,用于比较两个变量。

a. 比较基本数据类型

当 用于比较 byte, short, int, long, char, float, double, boolean 这八种基本数据类型时,它会直接比较它们的 是否相等。

示例代码:

Java中=与equals有何区别?-图2
(图片来源网络,侵删)
int a = 10;
int b = 10;
System.out.println(a == b); // 输出: true
double c = 3.14;
double d = 3.15;
System.out.println(c == d); // 输出: false
boolean e = true;
boolean f = false;
System.out.println(e == f); // 输出: false

在这个例子中,ab 的值都是 10,a == b 返回 true

b. 比较引用数据类型

当 用于比较对象(即引用数据类型)时,它比较的是这两个引用变量所指向的对象的 内存地址,只有当两个引用指向堆内存中的同一个对象时, 才会返回 true

示例代码:

// 创建一个字符串对象 "hello",并将其引用赋值给 s1
String s1 = new String("hello");
// 创建另一个新的字符串对象 "hello",并将其引用赋值给 s2
String s2 = new String("hello");
// s1 和 s2 是两个不同的对象,它们在内存中的地址不同
System.out.println(s1 == s2); // 输出: false
// 将 s2 的引用赋值给 s3,s1 和 s3 都指向同一个对象
String s3 = s1;
System.out.println(s1 == s3); // 输出: true

图解说明:

Java中=与equals有何区别?-图3
(图片来源网络,侵删)
s1 -----> [ "hello" ] <----- s3  (s1 和 s3 指向同一个内存地址)
s2 -----> [ "hello" ]          (s2 指向另一个新的内存地址)

因为 s1s2 是两个不同的对象实例,s1 == s2false


equals() 方法

equals() 是定义在 Object 类中的一个方法,这意味着所有 Java 类都继承了这个方法。

a. Object 类中的 equals()

Object 类中 equals() 方法的默认实现就是使用 来比较两个对象的内存地址。

// Object 类中的 equals() 方法源码(简化版)
public boolean equals(Object obj) {
    return (this == obj);
}

对于任何你没有重写 equals() 方法的类,equals() 的行为和 完全一样。

b. 重写 equals() 的常见类

很多 Java 核心类都重写了 equals() 方法,使其比较对象的内容,而不是内存地址,最典型的例子就是 String 类。

String 类中的 equals() String 类重写了 equals(),它会逐个字符地比较两个字符串对象的内容是否相同,而不管它们的内存地址是否一样。

示例代码:

String s1 = new String("hello");
String s2 = new String("hello");
// s1 和 s2 是不同的对象,内存地址不同
System.out.println(s1 == s2);       // 输出: false
// 但是它们的内容(字符串字面量)是相同的
System.out.println(s1.equals(s2));  // 输出: true

其他重写 equals() 的类:

  • 包装类Integer, Double, Long 等,它们比较的是包装的基本类型的值。
    Integer i1 = new Integer(100);
    Integer i2 = new Integer(100);
    System.out.println(i1 == i2);       // 输出: false (地址不同)
    System.out.println(i1.equals(i2));  // 输出: true (值相同)
  • 集合类ArrayList, HashMap 等,它们比较的是集合中的元素是否相同。
  • Date:比较的是两个日期对象表示的时间点是否相同。

为什么需要重写 equals()hashCode()

这是一个非常重要的问题,在 Java 中,如果一个对象要被放入 HashSet 或作为 HashMap 的键,那么它必须同时重写 equals()hashCode() 方法。

hashCode() 方法:返回一个对象的哈希码(一个整数),用于确定该对象在哈希表(如 HashMap)中的存储位置。

规则:

  1. 一致性a.equals(b)truea.hashCode() 必须等于 b.hashCode()
  2. 非强制反向a.hashCode() 等于 b.hashCode()a.equals(b) 不一定为 true(哈希冲突)。
  3. 程序运行期间:同一个对象的 hashCode() 必须返回相同的整数(前提是对象中用于 equals() 比较的信息没有被修改)。

举个例子说明为什么两者必须一起重写:

假设我们有一个 Person 类,我们想用 Person 对象作为 HashSet 的元素。

class Person {
    private String name;
    private int age;
    // 构造函数, getter, setter...
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // 只重写了 equals(),没有重写 hashCode()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }
}

现在我们测试 HashSet

Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
// p1 和 p2 内容相同,p1.equals(p2) 为 true
System.out.println(p1.equals(p2)); // 输出: true
HashSet<Person> set = new HashSet<>();
set.add(p1);
// 问题来了:HashSet 认为p1和p2是同一个对象(因为equals为true),
// 所以它不会把p2添加进去,但HashSet是通过hashCode()来查找位置的。
// 由于我们没有重写hashCode(),p1和p2的hashCode()可能不同(因为Object的hashCode基于内存地址)。
// 这就导致了逻辑矛盾:equals说我们是同一个,但hashCode说我们不是。
// 结果就是:HashSet可能把p2也加进去了,因为它在计算p2的hashCode后,找到了一个不同的桶。
// 这就破坏了Set集合不重复的原则。
// 实际运行结果取决于JVM,但这是潜在的错误根源。
System.out.println("Set size: " + set.size()); // 可能输出 1 或 2,这是不可预测的!

为了保证基于哈希的集合(HashSet, HashMap, Hashtable)的正确性,当你重写 equals() 方法时,必须同时重写 hashCode() 方法,以确保上述规则得到遵守。


最佳实践:如何正确重写 equals()hashCode()

现代 Java 开发推荐使用 IDE(如 IntelliJ IDEA 或 Eclipse) 自动生成这两个方法,它们能正确处理所有细节。

手动重写的模板如下:

import java.util.Objects;
public class Person {
    private String name;
    private int age;
    // ... 构造函数, getters, setters ...
    @Override
    public boolean equals(Object o) {
        // 1. 检查是否是同一个对象
        if (this == o) return true;
        // 2. 检查 o 是否为 null 或是否是同一个类
        if (o == null || getClass() != o.getClass()) return false;
        // 3. 向下转型
        Person person = (Person) o;
        // 4. 比较所有关键字段
        return age == person.age && Objects.equals(name, person.name);
    }
    @Override
    public int hashCode() {
        // 使用 Objects.hash() 方法,它会自动处理 null 并组合所有字段的哈希码
        return Objects.hash(name, age);
    }
}

总结表格

特性 运算符 equals() 方法
本质 Java 关系运算符 Object 类中定义的方法
基本数据类型 比较变量的 不可用(编译错误)
引用数据类型 比较对象的 内存地址 默认比较内存地址(Object 类的实现)
可重写性 不可重写 可以被任何子类重写
常见用法 比较基本类型、判断两个引用是否指向同一对象 比较对象的内容(如 String, Integer
使用场景 判断引用是否相同,或基本类型的值是否相等 当你需要判断两个对象在逻辑上是否“相等”时

记住这个黄金法则

  • 比较值,用 (基本类型)或 equals()(引用类型,前提是它被重写过)。
  • 判断是不是同一个对象(内存地址是否相同),用 。
分享:
扫描分享到社交APP
上一篇
下一篇