字符串字面量的行为令人困惑

31

以下代码中,字符串字面值的行为非常令人困惑。

我可以理解第1行、第2行和第3行是true,但为什么第4行是false呢?

当我打印它们的哈希码时,它们是相同的。

class Hello
{
   public static void main(String[] args)
   {
      String hello = "Hello", lo = "lo";
      System.out.print((Other1.hello == hello) + " ");     //line 1
      System.out.print((Other1.hello == "Hello") + " ");   //line 2
      System.out.print((hello == ("Hel"+"lo")) + " ");       //line 3
      System.out.print((hello == ("Hel"+lo)) + " ");         //line 4
      System.out.println(hello == ("Hel"+lo).intern());      //line 5
      System.out.println(("Hel"+lo).hashCode());   //hashcode is 69609650 (machine depedent)
      System.out.println("Hello".hashCode());       //hashcode is same WHY ??.
   }
}

class Other1 { static String hello = "Hello"; }

我知道==检查引用相等性并检查文字池中的字面值。 我知道equals()是正确的方法。 我想了解这个概念。

我已经查看了这个问题,但它没有清楚地解释。

我将非常感谢一份完整的解释。


7
+1 鼓励好奇心,不错的第一个问题。 - sanbhat
4
因为哈希码相等并不意味着对象的身份标识相同。请参阅Object.hashCode()的Javadoc。 - user207421
如果你知道这个,你就不会引起共振级联场景了! - Alex
11个回答

26

所有类型为String编译时常量表达式都将被放入字符串池中。

这实际上意味着:如果编译器可以(容易地)“计算”出String的值而不运行程序,则它将被放入池中(规则比这稍微复杂一些,并且有一些特殊情况,请参见上面的链接获取所有详细信息)。

对于第1-3行中的所有字符串都是如此。

"Hel"+lo不是编译时常量表达式,因为lo是一个非常量变量。

哈希码相同,因为字符串的哈希码仅取决于其内容。这是equals()hashCode()的契约要求。


好的,我想再澄清一件事,如果我们在程序中或者if条件语句中写像"Hello"这样的内容,那么它是一个新的对象还是字面量? - user2416387
1
像 "Hello" 这样的字面值始终是编译时常量表达式,因此它将从常量池中获取。 - Joachim Sauer
好的,假设问题是有多少个对象被创建了,那么我可以这样写:if(hello=="Hello"),所以当我输入"Hello"时,它不是一个对象,而是在编译时被放入常量池中,对吗? - user2416387
那么,如果要制作一个性能良好的应用程序,您应该知道在运行时创建了多少对象,如果您不知道如何做到这一点,我就不知道您是如何工作的。我使用Eclipse中的分析器,但我想知道如何在编译时计算它,因为Oracle在OCPJP认证中要求我们知道这一点!无论如何,谢谢,您的答案很棒,易于理解。 - user2416387
3
不,要制作一个良好的性能应用程序,你需要测量导致其变慢的因素并优化它们。我已经做过几次了,而问题是否一个 String是常量或不是从未成为性能问题的一部分。通过在循环中连接太多的String对象可能很容易成为问题,但这与这个问题无关(在我看来)。 - Joachim Sauer
显示剩余5条评论

2

以下是创建String对象的方式:

String str = new String("abcd");  // Using the new operator 
                                  // str is assigned with "abcd" value at compile time.

String str="abcd";         // Using string literal
                           // str is assigned with "abcd" value at compile time.

String str="ab" + "cd";    // Using string constant expression.
                           // str is assigned with "abcd" value at compile time.
String str1 = "cd";
String str = "ab"+str1;    // Using string expression.
                           // str is assigned with "abcd" value at run time only.

并且哈希码只会在运行时基于字符串对象的内容进行计算。


2

谁给这条评论点了踩,我认为这是正确的,@JoachimSauer,请您检查一下!! - user2416387

1

这是因为在这种情况下编译器还不够聪明,无法意识到它可以烧录同一字符串字面量。

Hashcode需要始终为等效的字符串返回相同的值(调用.equals()方法返回true),因此将返回相同的结果。


这篇文章讨论了使用字符串连接符创建的字符串是否存储在字符串池中。与此相关的信息可以在这里找到。 - ManMohan Vyas

0

这是因为以下代码

("Hel"+lo)) + " "

被内部转换为

new StringBuilder("Helo").append(new String(lo)).append(new String(" ")).toString()

所以你可以看到,通过不同的字符串实例帮助创建了一个全新的字符串实例。这就是为什么它们指向堆中不同的内存位置,因此你会得到 false。


1
那么 (hello == ("Hel"+"lo")) + " " --> true 吗? :-) 即使在这里,一个额外的字符串也被附加上去。 - sanbhat
错误:双引号未被附加,我在连接之前进行了检查,请查看括号。 - user2416387
顺便问一下,你有尝试查看反编译的代码吗?也许它可以帮助解决问题? - Saurabh
1
@saury:反编译可以帮助我们了解“做了什么”,但很少能回答“为什么这样做”的问题。 - Joachim Sauer

0

System.identityHashCode()将由默认方法hashCode()返回,通常通过将对象的内部地址转换为整数来实现。


String 重写了 ObjecthashCode 实现。 - mkl
字符串的哈希码是由其值计算得出的,完全正确。 - Monster

0

正如您所知道的那样... 这只是因为引用...当字符串来自池时,它将具有相同的引用...但当您进行操作时,将生成具有新引用的新字符串...

您可以查看此链接了解池化概念


2
那么,对于 hello == ("Hel" + "lo") --> true 的情况,它是如何适用的? - sanbhat
请您能否深入一点讲解。我不确定您是否正确。 - Craig
在第三行中,我正在进行操作 "Hel" + "lo",它将创建一个新的对象,但它仍然指向池中的 "Hello" 字符串。当添加字符串变量 "lo" 时,它将创建 "Hello",那么为什么它不会引用相同的池字面量? - user2416387
@sanbhat,你的评论加一。 - Saurabh

0

hashCode与对象的引用无关(==检查是一个引用比较器)。有可能存在两个对象,它们的hashCode返回相同的值,equals运算符返回true,但==返回false。这种情况发生在两个不同的对象,但具有相同的值。

我认为第4行返回false的原因是它是在运行时计算的值,因此是一个不同的字符串实例,具有不同的引用。


嗯,编译时会检查字符串池中的内容,但在使用lo进行字符串操作时,会在运行时检查。那么String hello = "Hello"也应在运行时进行检查,对吧? - user2416387
字符串 hello="Hello" 将在编译时完成,因为编译器在执行任何代码之前就知道需要放入该字符串的值 ("Hello")。 - Ren

0

字符串字面量保存在特殊的内存中,如果它们完全相同,则指向同一块内存映射。如果您不创建字面量字符串,则会创建一个新对象,因此它不会指向该内存,因此引用也不同。

intern() 方法告诉虚拟机将其放入共享的字符串字面量内存映射中,因此下次执行该字面量时,它将在那里搜索并指向它。


0
第3行和第4行之间的区别如下:
•通过常量表达式计算的字符串在编译时计算,然后被视为字面值。
•在运行时通过连接计算的字符串是新创建的,因此是不同的。
以上引用来自Java规范。如果需要更多澄清,请告知。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html#jls-3.10.5


嗯,我明白了,这是关于字符串的一团糟! - user2416387

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