使用“+”运算符的非字符串操作数后,结果字符串会进入字符串池吗?

after used non-string operand of "+" operator will the resulted string go to String pool?

我正在学习和准备 OCP 1Z0-815,我读了这本优秀的准备书:

Deshmukh,哈努曼特。 OCP Oracle Certified Professional Java SE 11 Programmer I Exam Fundamentals 1Z0-815:通过 OCP Java 11 Developer Certification Part 1 Exam 1Z0-815 的学习指南(第 99 页)。保温杯。 Kindle 教育。

String str = "hello";
for( int i = 0; i < 5; i + +) {
    str = str + i;
} 

The above creates one String object containing "hello" at the beginning and then two more in each iteration of the loop - a String containing the int value of i and the concatenated String. Thus, overall, the above code creates 1 + 2* 5 = 11 Strings. However, if you are asked how many String will be eligible to be garbage collected, the answer is not that easy. The Java Language Specification mentions in Section 15.8.1 that the non-string operand of the + operator is converted to a String at runtime but it does not clearly tell whether this String goes to the String pool (in which case it will not be garbage collected) or not.

Let me show you another piece of code:

String s = "hello";
int k = 100;
s = s + " 123" + k;

In this case, JLS section 15.8.1 clearly says that a compiler may avoid creating multiple strings altogether by making use of a StringBuilder. Thus, it is not possible to tell how many Strings will be created and how many will be eligible to be garbage collected...

上面的语句“...Java Language Specification在第15.8.1节中提到+运算符的非字符串操作数在运行时转换为String但没有明确说明是否这个字符串进入字符串池...”驱使我四处搜索,但我没有设法找到解释:据我所知 "new" 我明白它确实进入了字符串池并且所以它不是一个对象。因此,它根本不适用于垃圾收集。我说错了吗?

换句话说,据了解,每次循环都会在堆中的字符串池中产生一个常量字符串(Java 11 而不是 Java 7)。 hello0、hello1、hello2 等等,根本没有垃圾收集的候选对象。我知道 Garbage Colletion "clean" 对象由 new 运算符创建并且不作用于字符串池。

根据结论段落,“...第 15.8.1 节明确指出编译器可以通过使用 StringBuilder 避免创建多个字符串。因此,无法判断将创建多少个字符串以及有多少有资格被垃圾收集......”我假设它是说我无法发现在第二个代码示例(无循环)中的字符串池中创建了多少字符串,因为 StringBuilder 是在幕后使用的,我们知道 StringBuilder 在内存中操作连接,避免创建许多字符串文字。好吧,如果是这样的话,我仍然可以在第一个代码中假设每个循环都会产生一个文字字符串(hello0,hello1 ...),该字符串将进入字符串池并且实际上不符合垃圾收集的条件。我错了吗?

PS.: 你可能会评论说垃圾收集作用于字符串池,但我理解在实际情况下,字符串池中的文字字符串驻留了很长时间,以至于我们可以认为它永远不符合条件程序结束前的垃圾收集(" there is an implicit reference to the String object in the code of every method that uses the literal." and

as far as I don't see new I understand that indeed it goes to String pool and so it is not an object.

首先,Java语言规范没有具体提到字符串池。它实际上说的是,如果两个字符串值常量表达式是 equal,那么它们将是同一个对象。 (这涵盖了字符串文字,但也涵盖了 String s = "a" + "b"; 等情况)

字符串池是 >>a<< 执行此操作的机制,但 JLS 不要求特定机制。


new呢?

JLS 还说 new 运算符总是产生一个全新的对象:

"The value of a class instance creation expression is a reference to the newly created object of the specified class. Every time the expression is evaluated, a fresh object is created." (JLS 15.9.4)

这样做的结果是:

String a1 = new String("a");
System.out.println("a" == a1);   // prints "false"

这是必须做的,因为新的String不是新鲜的。由于字符串池是一种重复数据删除机制,我们可以得出结论,new String("a") 并未将新的 String 对象放入字符串池。


同样,+ 运算符创建一个新字符串:

"The result of string concatenation is a reference to a String object that is the concatenation of the two operand strings. The characters of the left-hand operand precede the characters of the right-hand operand in the newly created string." (JLS 15.18.1)

JLS 还说编译器可以优化涉及 +:

的表达式

"An implementation may choose to perform conversion and concatenation in one step to avoid creating and then discarding an intermediate String object. To increase the performance of repeated string concatenation, a Java compiler may use the StringBuffer class or a similar technique to reduce the number of intermediate String objects that are created by evaluation of an expression.

For primitive types, an implementation may also optimize away the creation of a wrapper object by converting directly from a primitive type to a string." (JLS 15.18.1)

但请注意,这种优化只允许在一个表达式中使用,不能跨多个语句使用。


JLS 没有提及其他字符串操作。对于它们的规范,我们需要参考 javadocs。在当前版本中,声明始终创建新字符串的唯一 String 方法是两个 join 方法。

但同样,唯一专门提到字符串池的String方法是intern方法。


规格就是这么说的。字符串实现实际上做了什么?

好吧,如果您检查 Java SE 实现的标准源代码,可以追溯到 Java 1.1,您会发现除了 intern 之外,没有任何 String 方法将对象放入字符串池。 None.


你也这么说:

I understand that in practical terms, literal string in String Pool resides for a so long time that we can consider it is never eligible for Garbage Collection before program ends.

大多数情况下都是如此。例外是您可以创建类加载器并使用它动态加载 类 的代码。如果那个类加载器变得不可访问,并且没有其他引用到它加载的 类,那么它的字符串文字可能无法访问......在程序结束之前。

如果您使用可以热加载 类 新版本的产品(例如 Web 容器),则可能会发生这种情况。


最后,如果我们看一下这个例子:

   String str = "hello";
   for (int i = 0; i < 5; i++) {
       str = str + i;
   } 

实践中:

  1. 我们第一次 运行 时,将(可能)为 "hello" 文字创建一个新的 String 对象。

  2. 在每次循环迭代中,至少会创建一个新的 String。可能会创建一个中间 String 来表示 i 的字符串值,但允许编译器对其进行优化。

  3. None 个中间 String 对象将被驻留/添加到字符串池中。

  4. 除了 "hello" 文字和最终值之外的所有内容都将无法访问。