连接多个字符串时将创建多少个 String 对象?

在一次采访中,我被问及将在给定问题上创建的对象数量:

String str1 = "First";
String str2 = "Second";
String str3 = "Third";
String str4 = str1 + str2 + str3;

我回答说,字符串池中将创建6个对象

3 将分别表示三个变量。
1 将用于(假设)。
1 将用于 。
1 将用于 ()。str1 + str2strstr2 + str3str + str3str = str1 + str2

我给出的答案是否正确?如果没有,正确的答案是什么?


答案 1

您的问题的任何答案都将取决于JVM实现和当前使用的Java版本。我认为在采访中问这是一个不合理的问题。

爪哇 8

在我的机器上,使用 Java 1.8.0_201 时,您的代码段会生成此字节码

L0
 LINENUMBER 13 L0
 LDC "First"
 ASTORE 1
L1
 LINENUMBER 14 L1
 LDC "Second"
 ASTORE 2
L2
 LINENUMBER 15 L2
 LDC "Third"
 ASTORE 3
L3
 LINENUMBER 16 L3
 NEW java/lang/StringBuilder
 DUP
 INVOKESPECIAL java/lang/StringBuilder.<init> ()V
 ALOAD 1
 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
 ALOAD 2
 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
 ALOAD 3
 INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
 INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
 ASTORE 4

这证明了正在创建5个对象(3个文本*,1个StringBuilder,1个由StringBuilder#toString动态生成的实例)。StringString

Java 12

在我的机器上,使用Java 12.0.2,字节码是

// identical to the bytecode above
L3
 LINENUMBER 16 L3
 ALOAD 1
 ALOAD 2
 ALOAD 3
 INVOKEDYNAMIC makeConcatWithConstants(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; [
  // handle kind 0x6 : INVOKESTATIC
  java/lang/invoke/StringConcatFactory.makeConcatWithConstants(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  // arguments:
  "\u0001\u0001\u0001"
 ]
 ASTORE 4

神奇地将“正确答案”更改为4个对象,因为不涉及中间部分。StringBuilder


*让我们更深入地挖掘一下。

12.5. 创建新的类实例

在以下情况下,可能会隐式创建新的类实例:

  • 加载包含字符串文本 (§3.10.5) 的类或接口可能会创建一个新的 String 对象来表示文本。(如果以前已暂存表示相同 Unicode 码位序列的字符串,则不会发生这种情况。

换句话说,当您启动应用程序时,字符串池中已经有对象。您几乎不知道它们是什么以及它们来自哪里(除非您扫描所有加载的类以查找它们包含的所有文本)。

毫无疑问,该类将作为基本 JVM 类加载,这意味着其所有文本都将被创建并放入池中。java.lang.String

让我们从 的源代码中随机选择一个片段,从中挑选几个文字,在程序的开头放置一个断点,然后检查池是否包含这些文字。String

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
               Constable, ConstantDesc {
    ...
    public String repeat(int count) {
        // ... 
        if (Integer.MAX_VALUE / count < len) {
            throw new OutOfMemoryError("Repeating " + len + " bytes String " + count +
                    " times will produce a String exceeding maximum size.");
        }
    }
    ...
}

他们确实在那里。

作为一个有趣的发现,这个IDEA的过滤有一个副作用:我正在寻找的子字符串也被添加到池中。应用 this.contains(“bytes String”) 后,池大小增加了一个(添加了“bytes String”)。

这给我们留下了什么?

在我们打电话之前,我们不知道是否被创建和实习,所以我们不能坚定地声明这条线创建了一个新的实例。"First"String str1 = "First";


答案 2

根据给定的信息,这个问题不能得到明确的回答。如 JLS§ 15.18.1 所述

...为了提高重复字符串串联的性能,Java 编译器可以使用该类或类似技术来减少通过计算表达式创建的中间 String 对象的数量。StringBuffer

这意味着答案至少取决于所使用的具体Java编译器。

我认为我们能做的最好的事情就是给出一个间隔作为答案:

  • 智能编译器可能能够推断出从未使用过 to,并在编译期间折叠串联,这样只创建一个 -object(引用的那个str1str3Stringstr4)
  • 创建的最大合理数目应为 5:一个用于 to、一个用于 和 一个用于 。Stringstr1str3tmp = str1 + str2str4 = tmp + str3

所以。。。我的回答是“一到五个物体之间的东西”。至于仅为此操作创建的对象总数...我不知道。这也可能取决于如何确切,例如 已实现。StringStringBuffer

顺便说一句:我想知道提出这些问题背后的原因是什么。通常,人们不需要关心这些细节。