Java 字符串比较结果为什么不同?把常量池、`==` 和 `equals()` 一次讲清

Elena
Elena
发布时间:2026-08-22| 38人浏览过
原创

前言

Java 字符串比较看似简单,实际最容易在常量池、堆对象和比较方式上混淆。本文从一段典型示例出发,拆开解释字面量创建、`new String()`、`==` 与 `equals()` 的差别,再补上拼接和 `null` 判等两个高频坑点,帮助你快速判断结果为什么不同,以及开发里该怎么写更稳妥。

同样都是内容为 "ja va" 的字符串,为什么有的比较结果是 true,有的却是 false?这类问题几乎都会绕回同一个核心:字符串到底是不是同一个对象,以及比较时看的是地址还是内容。下面从一段最常见的示例代码入手,把字符串常量池、new String() 的创建过程,以及 == 和 equals() 的区别一次理清。

为什么同样的字符串,比较结果会不同

先看一段代码,三个字符串的内容都是 "ja va",但比较结果并不一致:

public class Text{
public static void main(String[] args){
   String s1 = "ja va";
   String s2 = "ja va";
   String s3 = new String("ja va");
   System.out.println(s1==s2);//true
   System.out.println(s1 == s3);//false
   System.out.println(s1.equals(s3));//true
}

这段代码里,真正需要分开理解的是两件事:

  • 用字面量创建的字符串,是否会复用字符串常量池中的对象;
  • == 与 equals() 比较的到底是不是同一种东西。

只要把这两个点拆开,结果就不难解释了:s1 和 s2 指向同一个常量池对象,所以 s1 == s2 为 true;s3 是通过 new String("ja va") 创建出来的堆对象,因此和 s1 地址不同,s1 == s3 为 false;但它们内容一致,所以 s1.equals(s3) 仍然是 true。

Java 字符串常量池到底做了什么

Java 为了减少重复创建字符串带来的内存开销,提供了字符串常量池(String Constant Pool)。当代码里直接使用双引号字面量创建字符串时,JVM 会优先去常量池中查找是否已经存在相同内容的对象。

  • 如果常量池里已经有相同内容的字符串,就直接返回这个对象的引用;
  • 如果没有,就先在常量池中创建,再把引用返回给变量。

也就是说,下面这种写法通常会让两个变量指向同一个地址:

String s1 = "ja va";
String s2 = "ja va";

因此,s1 == s2 的结果是 true。这里的关键不是“值相同”,而是“它们恰好引用了常量池中的同一个对象”。

`new String()` 为什么会得到不同地址

如果把创建方式改成 new String("ja va"),情况就不同了。这个过程通常可以拆成两步理解:

展示 Java 字符串字面量与 new String 创建路径差异的白底信息图
字符串字面量与 `new String()`用字面量和用 `new String()` 创建字符串,看起来内容相同,实际引用路径并不一样。
  1. 先检查字符串常量池中是否已经存在 "ja va",不存在就先放进去;
  2. 再在堆内存中创建一个新的 String 对象,并把这个新对象的引用赋给变量。

因此,下面这行代码对应的 s3,保存的是堆中那个新对象的地址,而不是常量池对象本身:

String s3 = new String("ja va");

这就是为什么 s1 == s3 会得到 false。即便两个字符串内容完全相同,只要不是同一个对象引用,== 对于引用类型比较的结果就不会相等。

`==` 和 `equals()` 到底比较什么

`==` 比较的是值还是地址

== 的语义要分类型来看:

  • 对于基本数据类型,== 比较的是值本身;
  • 对于引用数据类型,包括 String,== 比较的是两个引用是否指向同一个对象,也就是内存地址是否一致。

所以在字符串场景里,== 不适合判断“文本内容是否相同”,它只能判断“是不是同一个对象”。

`equals()` 比较的是字符内容

equals() 方法定义在 Object 类中,而 String 对它进行了重写。重写后的逻辑,不再只看引用地址,而是逐字符比较两个字符串的内容是否完全一致。

这也是下面这句代码返回 true 的原因:

System.out.println(s1.equals(s3));//true

虽然 s1 和 s3 不是同一个对象,但它们保存的字符序列都是 "ja va",所以内容相等。

两个最常见的字符串比较陷阱

字符串拼接时,常量和变量的结果不一样

String s1 = "hello";
String s2 = "world";
String s3 = "helloworld";
String s4 = "hello" + "world";
String s5 = s1 + s2;
System.out.println(s3 == s4); // true
System.out.println(s3 == s5); // false

这组结果容易让人误判,原因在于两种拼接发生在不同阶段。

展示 Java 中 `==`、`equals()`、字符串拼接与 null 判等风险的白底信息图
字符串比较中的高频误区字符串判等最容易出错的,不是语法本身,而是比较目标、拼接阶段和 `null` 调用方式。
  • "hello" + "world" 是常量拼接,编译期就会被优化成 "helloworld",因此 s4 直接指向常量池中的同一个对象;
  • s1 + s2 是变量拼接,通常会通过 StringBuilder 在运行期生成新的字符串对象,结果放在堆中,所以 s5 与 s3 不是同一个引用。

因此,s3 == s4 是 true,而 s3 == s5 是 false。

`null` 调用 `equals()` 会直接抛异常

String s1 = null;
String s2 = "ja va";
System.out.println(s1.equals(s2)); // 会抛出NullPointerException
System.out.println(s2.equals(s1)); // 正常运行,返回false

这是另一个很常见的线上问题。s1 为 null 时,直接调用 s1.equals(s2) 会抛出 NullPointerException,因为此时根本没有对象可供调用方法。

更稳妥的写法,是让已知非空的常量或确定非空的变量去调用 equals(),例如:

"ja va".equals(s1)

这样即使 s1 是 null,也只会得到 false,不会触发空指针异常。

实际开发里怎么快速判断该用哪种比较

遇到字符串比较时,可以直接按下面的思路判断:

  • 想判断两个变量是不是同一个对象,用 ==;
  • 想判断两个字符串内容是否一致,用 equals();
  • 看到双引号字面量,先想到常量池复用;
  • 看到 new String()、变量拼接这类写法,先想到大概率会产生新的堆对象;
  • 涉及可能为 null 的字符串,优先使用常量调用 equals() 的方式避免空指针。

把这些规则记住后,再看字符串比较题或排查业务代码中的判等问题,基本就能迅速定位原因。

相关标签:

免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。

Python 相关产品和应用

全部下载
Selenium

Selenium

4.51.0 · Windows

用于浏览器自动化和 Web 测试的 Python 工具。

scikit-learn

scikit-learn

1.9.1 · Windows

常用 Python 机器学习算法与模型评估工具库。

Black

Black

26.10.1 · Windows

强调一致性的 Python 自动代码格式化工具。

Ruff

Ruff

0.17.1 · macOS

由 Rust 编写的高速 Python 代码检查和格式化工具。

pytest

pytest

9.1.1 · Windows

简洁而强大的 Python 自动化测试框架。

FastAPI

FastAPI

0.146.0 · Windows

高性能 Python API 框架,支持类型提示和自动接口文档。

Flask

Flask

3.1.3 · Windows

轻量灵活的 Python Web 应用框架。

Django

Django

6.1.2 · Windows

成熟的 Python Web 框架,适合构建内容站点和业务系统。

Spyder

Spyder

6.1.7 · Windows

面向科学计算与数据分析的 Python 集成开发环境。

SciPy

SciPy

1.18.1 · Windows

面向科学、工程和技术计算的 Python 算法库。