Java中为什么有些字符串可以使用引用比较(==)?

67

我有以下比较字符串的代码:str1str2不相等,这很容易理解因为它比较的是对象引用。但是为什么s1等于s2?

String s1 = "abc";
String s2 = "abc";
    
String str1 = new String("abc");
String str2 = new String("abc");
    
if (s1==s2)
    System.out.println("s1==s2");           
else
    System.out.println("s1!=s2");
    
if (str1==str2)
    System.out.println("str1==str2");           
else
    System.out.println("str1!=str2");
    
if (s1==str1)
    System.out.println("str1==s1");         
else
    System.out.println("str1!=s1");

输出:

  s1==s2
  str1!=str2
  str1!=s1

13
https://dev59.com/HFrUa4cB1Zd3GeqPkX-I,https://dev59.com/82865IYBdhLWcg3wA5uc,https://dev59.com/XFjUa4cB1Zd3GeqPQVMo,https://dev59.com/mkrSa4cB1Zd3GeqPZdK- - user166390
6个回答

92
字符串常量池会缓存所有字符串字面值,使它们在底层是相同的对象,这就是为什么你看到s1==s2输出结果相同的原因。这实际上是VM中的一种优化,避免每次声明字面量时都创建新的字符串对象,否则可能会非常昂贵!在str1==str2示例中,你明确告诉VM创建新的字符串对象,因此结果为false。
顺便说一下,对任何字符串调用intern()方法将其添加到常量池中,前提是等效的字符串不在常量池中(并返回它添加到的字符串)。然而,除非你确定要处理肯定会用作常量的字符串,否则不建议这样做,否则可能会创建难以跟踪的内存泄漏。

2
+1,但也值得注意的是,通常向常量池中添加大量字符串是一个不好的想法,因为它可能会导致难以检测的内存泄漏。只有对于一小部分真正要用作常量(例如HashMap键)的固定字符串才应这样做,而不是任意字符串数据。 - mikera

22

s1和s2是字符串常量。当你创建一个新的字符串常量时,编译器首先检查字符串池中是否存在表示相同内容的字符串常量。如果已经存在,则返回该常量,否则编译器创建一个新的常量。

当你创建字符串s2时,编译器从池中返回了常量s1,因为之前已经创建过了。这就是interning的原因,也就是让s1s2相同。


当你说“编译器创建一个新的”时,我有点困惑。据我所知,编译器是用于创建中间机器代码的,实际上并不运行(因此创建)任何内存中的对象。你是指编译器替换字符串字面量吗?请澄清一下。 - peakit
我现在没有任何支持文档,但我相信@Chandra Sekhar所指的是JIT(即时编译器),而不是javac编译器。 - Robert

11

这种现象是由字符串内部化引起的。

基本上,所有字符串字面量都被“缓存”并重用。


5

在Java中,由于string是不可变的,所以所有的字符串字面量都会被缓存以便重复使用。

如果你使用new()操作符创建字符串对象,它总是在堆内存中创建一个新的对象。另一方面,如果你使用字符串字面量语法创建对象,例如"Java",则如果它已经存在,则可能会从String池中返回现有对象(一种在Perm gen空间中的String对象缓存,最近的Java版本已将其移至堆空间),否则它将创建一个新的字符串对象并放入字符串池中以供将来重用。

String s1 = new String("java");
String s2 = new String("java");
String s3 = "java";
String s4 = "java";

输入图像描述

请参考此链接


5
在Java中,相同的常量字符串将会被重复利用。因此,s1s2指向同一个"abc"对象,因此s1==s2。但是当你使用new String("abc")时,另一个对象将会被创建。因此,s1 != str1

5

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接