Вопрос-ответ

Comparing strings with == which are declared final in Java

Сравнение строк с ==, которые объявлены окончательными в Java

У меня простой вопрос о строках в Java. Следующий сегмент простого кода просто объединяет две строки, а затем сравнивает их с ==.

String str1="str";
String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Выражение сравнения concat=="string" возвращает false как очевидное (я понимаю разницу между equals() и ==).


Когда эти две строки объявлены final вот так,

final String str1="str";
final String str2="ing";
String concat=str1+str2;

System.out.println(concat=="string");

Выражение сравнения concat=="string" в этом случае возвращает true. Почему final имеет значение? Это должно как-то быть связано с пулом стажеров или меня просто вводят в заблуждение?

Переведено автоматически
Ответ 1

Когда вы объявляете String (которая является неизменяемой) переменной как final и инициализируете ее с помощью выражения-константы времени компиляции, она также становится выражением-константой времени компиляции, и ее значение встроено компилятором, в котором она используется. Итак, во втором примере вашего кода после встраивания значений конкатенация строк преобразуется компилятором в:

String concat = "str" + "ing";  // which then becomes `String concat = "string";`

что при сравнении с "string" даст вам true, потому что строковые литералы интернированы.

Из JLS §4.12.4 - final Переменные:


Переменная примитивного типа или type String, которая является final и инициализируется константным выражением во время компиляции (§15.28), называется постоянной переменной.


Также из JLS §15.28 - Константное выражение:


Константные выражения типа во время компиляции String всегда "интернированы", чтобы совместно использовать уникальные экземпляры, используя метод String#intern().



Это не тот случай в вашем первом примере кода, где String переменные не являются final. Таким образом, они не являются постоянными выражениями во время компиляции. Операция конкатенации там будет отложена до времени выполнения, что приведет к созданию нового String объекта. Вы можете убедиться в этом, сравнив байтовый код обоих фрагментов кода.

Первый пример кода (неfinal версия) компилируется в следующий байтовый код:

  Code:
0: ldc #2; //String str
2: astore_1
3: ldc #3; //String ing
5: astore_2
6: new #4; //class java/lang/StringBuilder
9: dup
10: invokespecial #5; //Method java/lang/StringBuilder."<init>":()V
13: aload_1
14: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
17: aload_2
18: invokevirtual #6; //Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #7; //Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore_3
25: getstatic #8; //Field java/lang/System.out:Ljava/io/PrintStream;
28: aload_3
29: ldc #9; //String string
31: if_acmpne 38
34: iconst_1
35: goto 39
38: iconst_0
39: invokevirtual #10; //Method java/io/PrintStream.println:(Z)V
42: return

Очевидно, что это сохранение str и ing в двух отдельных переменных и использование StringBuilder для выполнения операции конкатенации.

Принимая во внимание, что ваш второй пример кода (final версия) выглядит следующим образом:

  Code:
0: ldc #2; //String string
2: astore_3
3: getstatic #3; //Field java/lang/System.out:Ljava/io/PrintStream;
6: aload_3
7: ldc #2; //String string
9: if_acmpne 16
12: iconst_1
13: goto 17
16: iconst_0
17: invokevirtual #4; //Method java/io/PrintStream.println:(Z)V
20: return

Таким образом, он напрямую вводит переменную final для создания строки string во время компиляции, которая загружается с помощью ldc операции на шаге 0. Затем второй строковый литерал загружается с помощью ldc операции на шаге 7. Это не требует создания какого-либо нового String объекта во время выполнения. Строка уже известна во время компиляции, и они интернированы.

Ответ 2

Согласно моему исследованию, все final String интернированы в Java. Из одного из сообщений в блоге:


Итак, если вам действительно нужно сравнить две строки с помощью == или != убедитесь, что вы вызываете метод String.intern() перед выполнением сравнения. В противном случае всегда отдавайте предпочтение String.equals(Строка) для сравнения строк.


Таким образом, это означает, что при вызове String.intern() вы можете сравнить две строки с помощью == оператора. Но здесь String.intern() в этом нет необходимости, потому что в Java final String они внутренне интернированы.

Вы можете найти дополнительную информацию о сравнении строк, используя оператор == и Javadoc для метода String.intern().

Также обратитесь к этому сообщению Stackoverflow для получения дополнительной информации.

Ответ 3

Если вы взглянете на эти методы

public void noFinal() {
String str1 = "str";
String str2 = "ing";
String concat = str1 + str2;

System.out.println(concat == "string");
}

public void withFinal() {
final String str1 = "str";
final String str2 = "ing";
String concat = str1 + str2;

System.out.println(concat == "string");
}

и его декомпилированных с javap -c ClassWithTheseMethods
версий, которые вы увидите

  public void noFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: new #19 // class java/lang/StringBuilder
9: dup
10: aload_1
11: invokestatic #21 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
14: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
17: aload_2
18: invokevirtual #30 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
...

и

  public void withFinal();
Code:
0: ldc #15 // String str
2: astore_1
3: ldc #17 // String ing
5: astore_2
6: ldc #44 // String string
8: astore_3
...

Итак, если строки не являются окончательными, компилятору придется использовать StringBuilder для объединения str1 и str2 так

String concat=str1+str2;

будут скомпилированы в

String concat = new StringBuilder(str1).append(str2).toString();

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


Также, если строки являются final, компилятор может предположить, что они никогда не изменятся, поэтому вместо использования StringBuilder он может безопасно объединить свои значения таким образом

String concat = str1 + str2;

может быть изменено на

String concat = "str" + "ing";

и объединены в

String concat = "string";

это означает, что concate станет строковым литералом, который будет интернирован в пул строк, а затем сравнен с тем же строковым литералом из этого пула в if инструкции.

Ответ 4

Концепция пула ограничений стека и строк введите описание изображения здесь

java string