运算符
是一个关系运算符,用于比较两个变量(或表达式)的值是否相等,它的行为取决于你比较的是什么类型的变量。

比较基本数据类型(Primitive Types)
当 用于比较基本数据类型(如 int, double, char, boolean 等)时,它会比较它们的值是否相等。
int a = 10; int b = 10; int c = 20; System.out.println(a == b); // 输出: true System.out.println(a == c); // 输出: false
对于基本类型, 比的是“值是否相等”。
比较引用数据类型(Reference Types)
当 用于比较引用数据类型(如所有自定义的类、String、Array 等)时,它会比较这两个引用是否指向同一个内存对象(即它们的内存地址是否相同),这被称为“浅比较”或“引用比较”。
class Person {
String name;
Person(String name) {
this.name = name;
}
}
public class Main {
public static void main(String[] args) {
Person p1 = new Person("Alice");
Person p2 = new Person("Alice");
Person p3 = p1;
System.out.println(p1 == p2); // 输出: false
// 解释:p1和p2是两个不同的对象,它们在内存中的地址不同。
System.out.println(p1 == p3); // 输出: true
// 解释:p3被赋值为p1的引用,所以它们指向同一个内存地址。
}
}
对于引用类型, 比的是“两个引用是否指向同一个对象(地址是否相同)”。

hashCode() 方法
hashCode() 是 Object 类中的一个方法,它的主要目的是为了提高哈希表(如 HashMap, HashSet, Hashtable)的存取效率。
- 返回值:一个
int类型的整数,被称为“哈希码”或“散列码”。 - 目的:它为对象在内存中生成一个相对唯一的“数字指纹”,这个指纹被哈希表用来确定对象应该被存放在哪个“桶”(bucket)中。
hashCode() 的约定(来自 Object 类的 JavaDoc)
为了确保哈希表(如 HashMap)的正确性和高效性,Java 规范要求任何重写了 equals() 方法的类,也必须重写 hashCode() 方法,并遵守以下三条约定:
- 一致性:在程序运行期间,只要对象的
equals()比较所用的信息没有被修改,那么对该对象多次调用hashCode()方法,必须 consistently 返回同一个整数。一次执行到另一次执行,这个整数可以不同。 - 相等的对象必须具有相同的哈希码:
a.equals(b)为true,a.hashCode()必须等于b.hashCode()。(这是最重要的一条!) - 不相等的对象,其哈希码不要求必须不同:
a.equals(b)为false,a.hashCode()和b.hashCode()不要求一定不相等。开发时应尽量让不相等的对象产生不同的哈希码,这能提高哈希表的性能(减少哈希冲突)。
和 hashCode() 的核心区别与联系
| 特性 | 运算符 | hashCode() 方法 |
|---|---|---|
| 类型 | 运算符 | 方法 |
| 基本类型:值是否相等。 引用类型:内存地址是否相同。 |
返回一个对象的哈希码(一个整数)。 | |
| 目的 | 判断两个变量在值或引用上是否相等。 | 为对象生成一个“数字指纹”,用于哈希表定位。 |
| 能否被重写 | 不能。 的行为是语言内置的,无法被重写。 | 可以,强烈建议在重写 equals() 时重写它。 |
| 默认行为 | 基本类型:比较值。 引用类型:比较内存地址。 |
返回对象的内存地址的某种形式(通常是 System.identityHashCode(this))。 |
为什么需要同时使用它们?—— HashMap 的工作原理
这是理解两者关系的关键,我们以 HashMap 为例,看看 hashCode() 和 equals() 是如何协同工作的,以及 在其中扮演的角色。
假设我们要将一个自定义对象 MyObject 存入 HashMap:

Map<MyObject, String> map = new HashMap<>();
MyObject key = new MyObject("some data");
map.put(key, "some value");
map.put(key, value) 的内部流程大致如下:
-
计算哈希码:
HashMap首先调用key.hashCode()方法,得到一个哈希码,12345。HashMap内部会使用这个哈希码来计算出这个对象应该被存放在哪个“桶”(bucket)中。index = hash & (n-1),n是HashMap的容量。
-
检查桶中的元素:
HashMap会去计算出的那个“桶”中查找。- 桶是空的,直接将新的键值对
(key, value)放入这个桶,结束。 - 桶不为空(意味着发生了“哈希冲突”,多个对象的哈希码相同或映射到了同一个桶),这时就需要进一步比较了。
-
使用
equals()进行精确比较:HashMap会遍历这个桶中的所有元素,用新来的key去和桶里已有的每个key调用equals()方法进行比较。equals()返回true:说明两个key在逻辑上是“相等”的。HashMap会认为这是对同一个key的重复操作,它会用新的value覆盖掉旧的value。equals()返回false:说明两个key逻辑上不相等,即使它们的哈希码相同(哈希冲突),HashMap也会认为这是两个不同的键,它会在这个桶中创建一个新的链表或红黑树节点,将新的键值对挂载上去。
在 HashMap 中的角色:
在 HashMap 的源码中,当比较桶中的节点时,它首先会用 进行一次快速检查:
// 伪代码
if (p.key == newKey) { // 使用 == 进行快速比较
// 找到完全相同的对象,直接覆盖
p.value = newValue;
return;
}
// 如果不是同一个对象,再调用 equals 进行逻辑比较
if (p.key.equals(newKey)) {
// 逻辑上相等,覆盖
p.value = newValue;
return;
}
使用 是一个非常高效的优化,因为如果两个引用指向的是同一个对象,那么它们的 equals() 结果也必然是 true,先用 判断,可以避免调用 equals() 方法带来的额外开销。
最佳实践与常见陷阱
重写 equals() 时必须重写 hashCode()
这是一个硬性规定,违反它会导致严重问题。
错误示例:
class User {
String name;
// ... 构造函数, getter, setter
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
// 忘记重写 hashCode()!
}
public class Main {
public static void main(String[] args) {
Map<User, String> map = new HashMap<>();
User user1 = new User("Alice");
User user2 = new User("Alice");
map.put(user1, "First User");
System.out.println(map.get(user2)); // 输出: null !!!
}
}
为什么输出 null?
map.put(user1, ...)时,user1.hashCode()被调用,假设返回100。user1被存放在哈希码为100的桶中。map.get(user2)时,user2.hashCode()被调用,由于我们没有重写hashCode(),它使用Object类的默认实现,这个实现基于对象的内存地址。user1和user2是两个不同的对象,所以它们的内存地址不同,导致user2.hashCode()返回的值(200)和user1.hashCode()不同。HashMap直接去哈希码为200的桶中查找,当然找不到user1,所以返回null。
即使 user1.equals(user2) 是 true,但在 HashMap 看来,它们因为哈希码不同,就是两个完全不同的键。
如何正确地重写 hashCode()?
手动编写既繁琐又容易出错,最佳实践是使用 IDE(如 IntelliJ IDEA 或 Eclipse)的自动生成功能,或者使用 Objects.hash() 工具类。
使用 Objects.hash() (推荐):
import java.util.Objects;
class User {
String name;
int id;
// ... 构造函数, getter, setter
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return id == user.id && Objects.equals(name, user.name);
}
@Override
public int hashCode() {
// IDE 生成或手动编写
return Objects.hash(name, id);
}
}
| 概念 | 核心要点 |
|---|---|
| - 基本类型:比较值。 - 引用类型:比较内存地址(是否为同一个对象)。 - 不可重写,是语言内置的。 |
|
hashCode() |
- 是一个方法,返回一个 int 类型的哈希码。- 主要用于哈希表( HashMap等),快速定位对象存储位置。- 可以重写,重写 equals() 时必须重写它。 |
| 核心关系 | - hashCode() 是为了性能,它快速缩小查找范围。- equals() 是为了精确性,它确定两个对象在逻辑上是否相等。- HashMap 依赖两者协同工作:先用 hashCode() 找到“桶”,再用 equals() 在桶里精确查找。 |
| 黄金法则 | 如果两个对象相等(a.equals(b) 为 true),那么它们的哈希码必须相等(a.hashCode() == b.hashCode())。(反过来不成立,哈希码相等的对象不一定相等。) |
理解 和 hashCode() 的区别与联系,是迈向Java高级开发、掌握集合框架和对象设计的关键一步。
