【问题标题】:How Java String pool works when String concatenation?字符串连接时Java字符串池如何工作?
【发布时间】:2017-05-18 02:17:27
【问题描述】:

注意:我不是要比较字符是否相等。因为我知道如何使用 String.equals() 方法。这个问题是关于字符串参考

当我开始学习 String 类及其属性为不变性等时,我正在为 OCA 考试学习。根据我对 String pool 的阅读或可能理解的是,当一个字符串Java 将此对象存储在他们所谓的 String pool 上,如果创建了具有相同值的新字符串,它将引用 String pool 上的字符串除了我们使用 new 关键字的情况,因为这会创建一个新的引用,即使两个字符串都包含相同的值。

例如:

String a = "foo";
String b = "foo";
String c = new String("foo");
boolean ab = (a == b); // This return true
boolean ac = (a == c); // This return false

要清楚这段代码的作用是在第一行代码中创建 String a = "foo" 并将其存储在 字符串池 中,然后在第二行代码中创建String b 和对 "foo" 的引用,因为它已经存在于 字符串池 中。但是第 3 行将创建这个字符串的新引用,无论它是否已经存在。这是一个关于正在发生的事情的小图形示例:

问题出在以下几行代码上。当通过连接创建字符串时,java 是否会做出不同的东西或简单的 == 比较器有另一种行为?

示例 A:

String a = "hello" + " world!";
String b = "hello world!";
boolean compare = (a == b); // This return true

示例 B:

a = "hello";
b = "hel" + "lo";
compare = (a == b); // This return true

示例 C:

a = "Bye";
a += " bye!";
b = "Bye bye!";
compare = (a == b); // This return false

观看代码运行:(http://ideone.com/fdk6KL)

发生了什么?

编辑

  1. 修复示例 B 中的错误:b = 'hel' + 'lo'

  2. 添加有关问题的说明。这不是比较问题,因为我知道String.equals() 的使用问题出在字符串池的引用上

【问题讨论】:

  • 字符串无法与“==”进行比较,条件如何为真?
  • @NathanHughes 更多的是个人知识。但是你有一个很好的观点
  • 字符串池不会被添加到每一个小连接中 - 想象一个循环,有人打印"Some Text" + i + "." 一千次。您认为这值得在字符串池中添加数千个条目吗?
  • 任何创建新String 实例的表达式(例如new String(...) 的调用或串联)都会创建一个新的、非实习的String 实例。 “字符串文字 - 或者更一般地说,作为常量表达式值的字符串(第 15.28 节) - 是“内部的”,以便使用方法 String.intern 共享唯一实例。” docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.5 否则必须明确调用intern。阅读精美手册。
  • 我想你想了解一下编译时常量这个术语。

标签: java string string-comparison object-reference string-pool


【解决方案1】:

"当字符串由连接创建时,java 会做些什么 不同或简单的 == 比较器有其他行为吗?”

不,它不会改变它的行为,会发生什么:

当连接两个字符串文字"a" + "b" 时,jvm 将两个值连接起来,然后检查字符串池,然后它意识到该值已经存在于池中,所以它只是简单地将这个引用分配给字符串。现在更详细:

看看下面这个简单程序的编译字节码:

public class Test  {    
    public static void main(String... args) {
        String a = "hello world!";
        String b = "hello" + " world!";
        boolean compare = (a == b);
    }
}

首先 JVM 加载字符串“hello world!”,然后将其推送到字符串池(在本例中),然后将其加载到堆栈中(ldc = 加载常量)[参见图片中的第 1 点]强>

然后它将在池中创建的引用分配给局部变量(astore_1)[参见图像中的第 2 点]

请注意,在字符串池中为此文字创建的引用是 #2 [参见图像中的第 3 点]

接下来的操作大致相同:in 连接字符串,将其推送到运行时常量池(本例中为字符串池),但随后它意识到具有相同内容的文字已经存在,因此它使用此引用 (# 2) 并赋值给一个局部变量 (astore_2)。

因此,当您执行 (a == b) 时为 true,因为它们都引用了字符串池 #2,即“hello world!”。

您的示例 C 有点不同,因为您使用的是 += 运算符,该运算符在编译为字节码时使用 StringBuilder 连接字符串,因此这会创建 StringBuilder 对象的新实例,从而指向不同的引用。 (字符串池与对象)

【讨论】:

  • 附加到您的代码:final String l = "Hello"; final String d = " Motto"; System.out.println((l+d) == "Hello Motto"); 这将打印true,但如果我们将关键字final 带到其中一个字符串,它将打印false。谢谢你的回答。
  • 由于两个ldc #2 指令都在已编译代码中,因此编译器执行了字符串连接和常量折叠。 JVM 看到两条 ldc 指令指向同一个池条目 (#2)。它不必“意识到”这些常数是相同的,这是显而易见的。在运行时,不会发生字符串连接。 JVM 甚至不知道该常量的第二次使用是在编译时进行字符串连接。
  • 同一主题的有趣阅读 - quora.com/…
  • 关于“示例 C”的答案不正确。第一:编译为字节码时 += 运算符不使用 StringBuilder 连接字符串。它使用 StringConcatFactory.makeConcatWithConstants。第二:它返回 false 是因为使用 += 运算符时,新创建的字符串不会添加到字符串池中。使用文字字符串表示法(您的双引号文本)创建的字符串和标记为最终变量的字符串 - 被添加到字符串池中。连接的字符串作为堆上的新对象创建。与字符串池中的字符串相比,它们拥有不同的引用。
  • 好的,谢谢你的评论,然后我明白了,当连接在同一行声明中时,编译器只将代码翻译成一个文字,以简化。也许对我们来说,我们在同一行中有几个变量,但对于编译它只有一个(替换任何变量,它最终是一个文字),因此,比较是正确的;另一方面,当我们在不同的行中进行连接时,它不会发生,因为它不是文字,所以,这个变量不会位于池字符串中。
【解决方案2】:
String a = "ef";
String b = "cd" + a;
        
System.out.println("cdef"==b); // false
        
String c = "cd" + "ef";
        
System.out.println("cdef"==c); // true

intern()方法在String对象上被调用时,它会尝试在池中寻找具有相同字符序列的String。

如果字符串池已经有一个具有相同值的字符串,则从池中返回该字符串的引用,否则将字符串添加到池中,并返回引用。

字符串连接只会在表达式是常量表达式时对字符串进行内部处理

"cd" + a // is not a Constant Expression

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-01-23
    • 2010-12-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多