java中`==`和`.equals()`区别

java中==.equals()区别

前言

这是一个 Java 领域经典的基础问题。

一句话核心区别

== 比较的是"是不是同一个东西"(内存地址),
.equals() 比较的是"长得一不一样"(内容/数值)。

Java 内存可以将其简单分为两块:

  • 栈内存(Stack): 存放基本数据类型的值,以及对象变量的"引用地址"(可以理解为开门的钥匙)。
  • 堆内存(Heap): 使用 new 关键字创建的具体对象(可以理解为具体的房子),存放在这里。

一、 核心概念:物理地址 vs 逻辑内容

1.1 == 操作符:物理视角的"唯一性"

== 是 Java 的原生运算符,它执行的是最底层的"位比对"。

  • 基本数据类型 :比较的是
  • 引用数据类型 :比较的是内存地址(即堆内存中的物理位置)。

1.2 .equals() 方法:逻辑视角的"相似性"

.equals()java.lang.Object 类中定义的成员方法。

  • 默认逻辑 :如果类没有重写该方法,其源代码内部依然是使用 == 进行比较。
  • 重写逻辑 :Java 官方类(如 String, Integer, Date)都重写了此方法,将其改为内容比较

二、可视化分析

2.1 比较基本数据类型(int, double, boolean 等)

基本类型的值直接存在栈中,因此 == 直接比较其数值是否相等。

java 复制代码
int a = 100;
int b = 100;
// 结果是 true,因为数值都是 100
System.out.println(a == b);

2.2 比较引用数据类型(对象,如 String, Integer 等)

变量(钥匙)存在栈中,实际对象(房子)存在堆中。此时,== 比较的是这两把钥匙是不是同一把,即它们是否指向堆内存里的同一个物理地址

java 复制代码
// 在堆内存里建了一栋房子叫 "hello",钥匙交给了 s1
String s1 = new String("hello"); 
// 在堆内存里又建了一栋新房子也叫 "hello",钥匙交给了 s2
String s2 = new String("hello"); 

// 结果是 false!
// 虽然两栋房子外观内容一模一样,但它们物理上是两栋不同的房子。
System.out.println(s1 == s2);

// 结果是 true!
// .equals() 不管钥匙/地址,直接对比两栋房子里的内容,发现都是 "hello",即判定相等。
System.out.println(s1.equals(s2));

2.3 字符串常量池机制

在实际开发中,如果不使用 new 关键字,而是直接赋值字符串,会触发 Java 的底层优化机制------字符串常量池.

java 复制代码
String str1 = "hello";
String str2 = "hello";

System.out.println(str1 == str2);      // 结果:true 
System.out.println(str1.equals(str2)); // 结果:true

⚠️ 为什么此时 == 变成了 true

出于节省内存的考虑,当你写 str1 = "hello" 时,Java 会在常量池里建一栋 "hello" 的房子。

当你再写 str2 = "hello" 时,Java 发现池子里已经有同样的房子了,就不会新建,而是把同一把钥匙(物理地址)同时交给了 str1str2

此时,它们不仅内容相同,物理地址也完全一致。

2.4 引用赋值

当你执行 User u2 = u1; 时,Java 虚拟机(JVM)并没有 在堆内存(Heap)里创建一个新的 User 对象。它仅仅是把 u1 变量里存储的十六进制内存地址 (比如 0x105)复制了一份,存到了 u2 变量里。

java 复制代码
// 1. 在堆中创建一个对象,u1 得到地址 0x105
User u1 = new User("Li");

// 2. 引用赋值:把 u1 的地址直接传给 u2
User u2 = u1;

// 3. 结果验证
System.out.println("u1 == u2: " + (u1 == u2));         // true (地址完全相同)
System.out.println("u1.equals(u2): " + u1.equals(u2)); // true (内容必然相同)

// 4. 侧面验证:修改 u2 会影响 u1 吗?
u2.setName("Wang");
System.out.println("修改u2后,u1的名字是: " + u1.getName()); // 输出 "Wang"

三、常见问题

2.1 自定义类必须同时重写 equals() 和 hashCode()

否则 HashMap、HashSet 等集合会出错。

java 复制代码
@Override
public boolean equals(Object o) { ... }

@Override
public int hashCode() { ... }

2.2 Integer 缓存机制(-128~127)

java 复制代码
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true

Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false

⚠️ 建议:始终使用 .equals() 比较对象,除非你明确需要判断地址。

四、常见问题对照表

问题 原因 解决方案
new String()== 返回 false 堆内存创建了两个不同对象 使用 .equals() 比较内容
字面量字符串 == 返回 true 字符串常量池复用 理解常量池机制,对象比较仍推荐 .equals()
HashMap 存自定义对象时 key 重复/丢失 未重写 equals()hashCode() 必须同时重写两者
Integer 127 和 128 == 结果不同 IntegerCache 缓存 -128~127 统一使用 .equals()
自定义类用 == 比较始终 false 引用类型默认比较地址 重写 .equals()

总结

Java 中 ==.equals() 的核心区别如下:

  • ==:比较内存地址(物理层面)
  • .equals():比较内容(逻辑层面,默认继承自 Object,重写后按需比较)
  • 字符串常量池是导致 == 结果不同的常见特例
  • 自定义类必须同时重写 equals()hashCode()
相关推荐
小小李程序员4 小时前
Langchain4j工具调用获取不到ThreadLocal
java·后端·ai
qq_283720054 小时前
Python Celery + FastAPI + Vue 全栈异步任务实战
vue.js·python·fastapi
2401_885885045 小时前
营销推广短信接口集成:结合营销策略实现的API接口动态变量填充方案
前端·python
zs宝来了5 小时前
AQS详解
java·开发语言·jvm
telllong5 小时前
Python异步编程从入门到不懵:asyncio实战踩坑7连发
开发语言·python
wjs20247 小时前
JavaScript 条件语句
开发语言
lulu12165440787 小时前
Claude Code Harness架构技术深度解析:生产级AI Agent工程化实践
java·人工智能·python·ai编程
阿里加多7 小时前
第 1 章:Go 并发编程概述
java·开发语言·数据库·spring·golang