Сколько объектов 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).

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

7 ответов

Решение

Любой ответ на ваш вопрос будет зависеть от реализации 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.");
        }
    }
    ...
}

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

Как интересная находка, у этой фильтрации IDEA есть побочный эффект: подстроки, которые я искал, также были добавлены в пул. Размер пула увеличился на один ( "bytes String" был добавлен) после того, как я подал заявку this.contains("bytes String") ,

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • один для str1 + str2

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

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

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

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

String s1 = new String("one")

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

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

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

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

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

Другие вопросы по тегам