Khi bạn khai báo một biến String
( không thay đổi ) là final
và khởi tạo nó bằng biểu thức hằng số thời gian biên dịch, nó cũng trở thành biểu thức hằng số thời gian biên dịch và giá trị của nó được trình biên dịch sử dụng. Vì vậy, trong ví dụ mã thứ hai của bạn, sau khi nội tuyến các giá trị, phép nối chuỗi được dịch bởi trình biên dịch thành:
String concat = "str" + "ing"; // which then becomes `String concat = "string";`
mà khi so sánh "string"
sẽ cung cấp cho bạn true
, bởi vì chuỗi ký tự được thực tập .
Từ JLS §4.12.4 - final
Biến :
Một biến của kiểu nguyên thủy hoặc kiểu String
, được final
khởi tạo với biểu thức hằng số thời gian biên dịch (§15.28), được gọi là biến không đổi .
Cũng từ JLS §15.28 - Biểu thức không đổi:
Các biểu thức hằng số thời gian biên dịch kiểu String
luôn được "tập trung" để chia sẻ các thể hiện duy nhất, sử dụng phương thứcString#intern()
.
Đây không phải là trường hợp trong ví dụ mã đầu tiên của bạn, trong đó String
biến không final
. Vì vậy, chúng không phải là một biểu thức hằng số thời gian biên dịch. Hoạt động nối sẽ có độ trễ cho đến thời gian chạy, do đó dẫn đến việc tạo ra một String
đối tượng mới . Bạn có thể xác minh điều này bằng cách so sánh mã byte của cả hai mã.
Ví dụ mã đầu tiên (không phải final
phiên bản) được biên dịch thành mã byte sau:
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
Rõ ràng nó đang lưu trữ str
và ing
trong hai biến riêng biệt, và sử dụng StringBuilder
để thực hiện thao tác nối.
Trong khi đó, ví dụ mã thứ hai ( final
phiên bản) của bạn trông như thế này:
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
Vì vậy, nó trực tiếp nội tuyến biến cuối cùng để tạo Chuỗi string
tại thời gian biên dịch, được tải bởi ldc
hoạt động trong bước 0
. Sau đó, chuỗi ký tự thứ hai được tải bằng ldc
thao tác trong bước 7
. Nó không liên quan đến việc tạo ra bất kỳ String
đối tượng mới nào trong thời gian chạy. Chuỗi đã được biết đến tại thời điểm biên dịch và chúng được thực hiện.