同样都是内容为 "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"),情况就不同了。这个过程通常可以拆成两步理解:

- 先检查字符串常量池中是否已经存在
"ja va",不存在就先放进去; - 再在堆内存中创建一个新的
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
这组结果容易让人误判,原因在于两种拼接发生在不同阶段。

"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()的方式避免空指针。
把这些规则记住后,再看字符串比较题或排查业务代码中的判等问题,基本就能迅速定位原因。













