Сколько объектов String будет создано при объединении нескольких строк?

В интервью меня спросили о количестве объектов, которые будут созданы по данной проблеме:

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

Правильный ли ответ я дал? Если нет, каков правильный ответ?

Ответы

Ответ 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 динамически создаваемый экземпляр String от 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

который волшебным образом меняет "правильный ответ" на 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.");
        }
    }
    ...
}

Они действительно там.

As an interesting find, this IDEA filtering has a side effect: the substrings I was looking for have been added to the pool as well. The pool size increased by one ([TG410] was added) after I applied [TG411].

Куда это нас приведет?

Мы не знаем, был ли "First" создан и интернирован, прежде чем мы вызовем String str1 = "First";, поэтому мы не можем твердо заявить, что линия создает новый экземпляр.

Ответ 2

С предоставленной информацией, вопрос не может определенно ответить. Как указано в JLS, §15.18.1:

... Чтобы повысить производительность многократной конкатенации строк, компилятор Java может использовать класс StringBuffer или аналогичный метод для сокращения числа промежуточных объектов String, которые создаются путем вычисления выражения.

Это означает, что ответ зависит по крайней мере от конкретного используемого компилятора Java.

Я думаю, что лучшее, что мы можем сделать, это дать интервал в качестве ответа:

  • умный компилятор может сделать вывод, что str1 - str3 никогда не используются, и свернуть конкатенацию во время компиляции, так что создается только один String -object (тот, на который ссылается str4)
  • Максимальное разумное число созданных String должно быть 5: по одному для str1 - str3, по одному для tmp = str1 + str2 и по одному для str4 = tmp + str3.

Итак... мой ответ будет "что-то от одного до пяти String -object с". Что касается общего количества объектов, созданных только для этой операции... Я не знаю. Это также может зависеть от того, как именно, например, StringBuffer реализован.

Как в стороне: интересно, какова причина того, чтобы задавать такие вопросы. Обычно не нужно заботиться об этих деталях.

Ответ 3

Java 8, вероятно, создаст 5 объектов:

  • 3 для 3 литералов
  • 1 StringBuilder
  • 1 для сцепленного String

В Java 9 все изменилось, хотя конкатенация String больше не использует StringBuilder.

Ответ 4

Должно быть 5:

  • три для трех литералов (присвоенных str1, str2 и str3)

  • один для str1 + str2

  • один для (result from the previous operation) + str3 (назначен для str4)

Ответ 5

Соответствующая реализация Java может объединять строки любым количеством способов, во время выполнения или во время компиляции, требуя любое количество объектов времени выполнения, включая нулевые объекты, если обнаруживает, что результат не нужен во время выполнения.

Ответ 6

4-х строковый объект будет создан в пуле строковых констант. 3 для литералов и 1 с конкатенацией.

если мы используем

String s1 = new String("one")

это создаст два объекта, один в постоянном пуле и один в динамической памяти.

если мы определим:

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

это создаст два объекта, один в постоянном пуле и один в динамической памяти.

Ответ 7

Операция конкатенации не создает столько объектов String. Он создает StringBuilder, а затем добавляет строки. Таким образом, может быть 5 объектов, 3 (переменные) + 1 (sb) + 1 (объединенная строка).