Коли ви оголошуєте змінну 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
об’єкта під час виконання. Рядок уже відомий під час компіляції, і вони інтерновані.