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

How many String objects would be created when concatenating multiple Strings?

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

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

我回答说在字符串池中会创建 6 个对象

3 would be for each of the three variables.
1 would be for str1 + str2 (let's say str).
1 would be for str2 + str3.
1 would be for the str + str3 (str = str1 + str2).

我给的答案正确吗?如果不是,正确答案是什么?

应该是5:

  • 三个用于三个文字(分配给 str1str2str3

  • 一个 str1 + str2

  • 一个给(result from the previous operation) + str3(分配给str4

Java 8 可能会创建 5 个对象:

  • 3 代表 3 个文字
  • 1 StringBuilder
  • 1 表示串联 String

尽管 Java 9 things changedString 连接不再使用 StringBuilder

根据给定的信息,无法明确回答问题。正如 JLS, §15.18.1 中所述:

... 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.

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

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

  • 一个聪明的编译器可能能够推断出 str1str3 从未被使用过,并在编译期间折叠连接,这样只创建一个 String-object(一个被 str4)
  • 引用
  • 创建的 String 的最大合理数量应为 5:str1str3 各一个,tmp = str1 + str2 一个,[=19= 一个].

所以...我的答案是 "something between one to five String-objects"。至于只为此操作创建的对象总数……我不知道。这也可能取决于例如StringBuffer 已实施。

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

串联操作不会创建那么多 String 对象。它创建一个 StringBuilder 然后附加字符串。所以可能有5个对象, 3(变量)+ 1(sb)+ 1(串联字符串)。

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

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 String 文字*,1 StringBuilder, 1 dynamically produced String instance by StringBuilder#toString)。

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

其中 magically 将 "the correct answer" 更改为 4 个对象,因为不涉及中间 StringBuilder


*让我们深入挖掘一下。

12.5. Creation of New Class Instances

A new class instance may be implicitly created in the following situations:

  • Loading of a class or interface that contains a string literal (§3.10.5) may create a new String object to represent the literal. (This will not occur if a string denoting the same sequence of Unicode code points has previously been interned.)

也就是说,当你启动一个应用程序时,String池中已经有对象了。您几乎不知道它们是什么以及它们来自哪里(除非您扫描所有加载的 classes 以查找它们包含的所有文字)。

java.lang.String class 无疑将作为基本 JVM class 加载,这意味着它的所有文字都将被创建并放入池中。

让我们从 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"; 之前是否被创建和驻留,所以我们不能肯定地说该行创建了一个新实例。

一致的 Java 实现可以在 运行 时间或编译时以任意数量的方式连接字符串,需要任意数量的 运行 时间对象,包括零对象如果在 运行 次检测到不需要结果。

将在字符串常量池中创建4个字符串对象。 3 个用于文字,1 个用于连接。

如果我们使用

String s1 = new String("one")

它将创建两个对象,一个在常量池中,一个在堆内存中。

如果我们定义:

String s1 = "one";
String s2 = new String("one");

它将创建两个对象,一个在常量池中,一个在堆内存中。