38

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

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

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

3 将针对三个变量中的每一个。
1 将用于str1 + str2(比方说str)。
1 将用于str2 + str3.
1 代表str + str3( str = str1 + str2)。

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

4

7 回答 7

34

您的问题的任何答案都取决于 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 个对象String(3 个文字*、1 个StringBuilder、1 个由 动态生成String的实例StringBuilder#toString)。

爪哇 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 代码点序列的字符串先前已被保留,则不会发生这种情况。)

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

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

让我们从 的源代码中随机选择一个片段,从中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 的过滤有一个副作用:我正在寻找的子字符串也已添加到池中。在我申请后,池大小增加了一个("bytes String"已添加)this.contains("bytes String")

这让我们何去何从?

我们不知道"First"在调用之前是否被创建和实习String str1 = "First";,所以我们不能坚定地声明该行创建了一个新实例。

于 2019-08-23T18:13:03.290 回答
19

有了给定的信息,这个问题不能肯定地回答。如JLS 中所述,§15.18.1

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

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

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

  • 智能编译器可能能够推断出str1从未str3使用过并在编译期间折叠串联,以便仅String创建一个 -object(由 引用的那个str4
  • 创建的 s 的最大合理数量String应为 5:一个用于str1to str3,一个用于tmp = str1 + str2和一个用于str4 = tmp + str3

所以......我的答案是“一到五个对象之间的东西String”。至于为此操作创建的对象总数......我不知道。这也可能取决于 eg 是如何StringBuffer实现的。

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

于 2019-08-23T18:00:39.623 回答
9

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

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

但是随着 Java 9的变化String连接不再使用StringBuilder

于 2019-08-23T17:54:48.713 回答
3

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

于 2019-08-24T17:40:47.837 回答
3

应该是 5:

  • 三个用于三个文字(分配给str1,str2str3

  • 一个为str1 + str2

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

于 2019-08-23T17:52:33.497 回答
2

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

如果我们使用

String s1 = new String("one")

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

如果我们定义:

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

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

于 2019-08-25T13:35:48.703 回答
1

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

于 2019-08-23T18:10:43.093 回答