Сравнение строк с ==, которые объявлены окончательными в 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
Концепция пула ограничений стека и строк