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()
相关推荐
xmjd msup1 天前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
952361 天前
SpringBoot统一功能处理
java·spring boot·后端
有一个好名字1 天前
工具即双手 —— 从 Bash 到 Tool Dispatch Map
开发语言·chrome·bash
Lyyaoo.1 天前
优惠券秒杀业务分析
java·开发语言
消失的旧时光-19431 天前
统一并发模型:线程、Reactor、协程本质是一件事(从线程到协程 · 第6篇·终章)
java·python·算法
勿忘初心12211 天前
Java 国密 SM4 加密工具类实战(Hutool + BouncyCastle)|企业级数据加密 + 兼容 JDK8
java·数据安全·数据加密·后端开发·企业级开发·国密 sm4
庞轩px1 天前
第8篇:原子类与CAS底层原理——无锁并发的实现
java·cas·乐观锁·aba·无锁编程·自旋
rleS IONS1 天前
SpringBoot中自定义Starter
java·spring boot·后端
DevilSeagull1 天前
MySQL(2) 客户端工具和建库
开发语言·数据库·后端·mysql·服务
苍煜1 天前
慢SQL优化实战教学
java·数据库·sql