核心总结(先说结论)
-
:
(图片来源网络,侵删)- 基本数据类型:比较的是 值 是否相等。
- 引用数据类型:比较的是 内存地址 是否相等,即判断两个引用是否指向同一个对象。
-
equals():- 它是一个方法,不是运算符。
- 默认行为:如果没有被重写,
equals()的作用和 一样,也是比较两个对象的内存地址。 - 重写后:我们通常在自定义类中重写
equals()方法,使其比较对象的 内容(属性值) 是否相等。
运算符
是 Java 中的一个关系运算符,用于比较两个变量。
a. 比较基本数据类型
当 用于比较 byte, short, int, long, char, float, double, boolean 这八种基本数据类型时,它会直接比较它们的 值 是否相等。
示例代码:

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
在这个例子中,a 和 b 的值都是 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
图解说明:

s1 -----> [ "hello" ] <----- s3 (s1 和 s3 指向同一个内存地址)
s2 -----> [ "hello" ] (s2 指向另一个新的内存地址)
因为 s1 和 s2 是两个不同的对象实例,s1 == s2 为 false。
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)中的存储位置。
规则:
- 一致性:
a.equals(b)为true,a.hashCode()必须等于b.hashCode()。 - 非强制反向:
a.hashCode()等于b.hashCode(),a.equals(b)不一定为true(哈希冲突)。 - 程序运行期间:同一个对象的
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()(引用类型,前提是它被重写过)。 - 想判断是不是同一个对象(内存地址是否相同),用 。
