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