Когда вы объявляете String
(которая является неизменной ) переменную как final
, и инициализируете ее с помощью константного выражения во время компиляции, она также становится константным выражением во время компиляции, и ее значение указывается компилятором, где она используется. Итак, во втором примере кода после вставки значений компиляция строк преобразуется компилятором в:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
который по сравнению с "string"
даст вам true
, потому что строковые литералы интернированы .
Из JLS §4.12.4 - final
Переменные :
Переменная примитивного типа или типа 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
Таким образом, он напрямую указывает на конечную переменную для создания String string
во время компиляции, которая загружается ldc
операцией в шаге 0
. Затем второй строковый литерал загружается ldc
операцией в шаге 7
. Это не предполагает создания какого-либо нового String
объекта во время выполнения. Строка уже известна во время компиляции, и они интернированы.