Hiệu quả cuối cùng so với cuối cùng - Hành vi khác nhau


104

Cho đến nay, tôi nghĩ rằng hiệu quả cuối cùngcuối cùng ít nhiều tương đương và JLS sẽ xử lý chúng tương tự nếu không giống nhau trong hành vi thực tế. Sau đó, tôi tìm thấy kịch bản giả tạo này:

final int a = 97;
System.out.println(true ? a : 'c'); // outputs a

// versus

int a = 97;
System.out.println(true ? a : 'c'); // outputs 97

Rõ ràng, JLS tạo ra sự khác biệt quan trọng giữa hai điều này ở đây và tôi không chắc tại sao.

Tôi đọc các chủ đề khác như

nhưng họ không đi vào chi tiết như vậy. Rốt cuộc, ở mức độ rộng hơn, chúng dường như tương đương nhau. Nhưng đào sâu hơn, chúng dường như khác nhau.

Điều gì đang gây ra hành vi này, bất cứ ai có thể cung cấp một số định nghĩa JLS giải thích điều này?


Chỉnh sửa: Tôi đã tìm thấy một tình huống liên quan khác:

final String a = "a";
System.out.println(a + "b" == "ab"); // outputs true

// versus

String a = "a";
System.out.println(a + "b" == "ab"); // outputs false

Vì vậy, chuỗi xen kẽ cũng hoạt động khác nhau ở đây (tôi không muốn sử dụng đoạn mã này trong mã thực, chỉ tò mò về hành vi khác nhau).


2
Câu hỏi rất thú vị! Tôi mong rằng Java sẽ hoạt động giống nhau cho cả hai trường hợp, nhưng giờ tôi đã ngộ ra. Tôi đang tự hỏi bản thân liệu đây có phải là hành vi luôn luôn hay nó khác với các phiên bản trước
Lino

8
@Lino Các từ ngữ cho các báo cuối cùng trong câu trả lời tuyệt vời dưới đây là giống tất cả các cách trở lại Java 6 : "Nếu một trong các toán hạng là loại T nơi Tbyte, shorthoặc char, và các toán hạng khác là một biểu hiện liên tục của nhập intcó giá trị có thể biểu diễn trong kiểu T , thì kiểu của biểu thức điều kiện là T. " --- Thậm chí còn tìm thấy một tài liệu Java 1.0 tại Berkeley. Cùng một văn bản . --- Vâng, nó luôn luôn như vậy.
Andreas

1
Cách bạn "tìm thấy" mọi thứ thật thú vị: P Không có gì phải lo :)
phant0m

Câu trả lời:


65

Trước hết, chúng ta chỉ nói về các biến cục bộ . Hiệu quả cuối cùng không áp dụng cho các lĩnh vực. Điều này rất quan trọng, vì ngữ nghĩa của finalcác trường rất khác biệt và phải tuân theo các tối ưu hóa trình biên dịch nặng và các hứa hẹn về mô hình bộ nhớ, hãy xem $ 17,5,1 về ngữ nghĩa của các trường cuối cùng.

Ở cấp độ bề mặt finaleffectively finalcác biến cục bộ thực sự giống hệt nhau. Tuy nhiên, JLS phân biệt rõ ràng giữa hai thứ thực sự có nhiều hiệu ứng trong những tình huống đặc biệt như thế này.


Tiền đề

Từ JLS§4.12.4 về finalcác biến:

Một biến hằng là một finalbiến kiểu nguyên thủy hoặc kiểu String được khởi tạo bằng biểu thức hằng ( §15.29 ). Việc một biến có phải là một biến hằng số hay không có thể có ý nghĩa liên quan đến việc khởi tạo lớp ( §12.4.1 ), khả năng tương thích nhị phân ( §13.1 ), khả năng truy cập lại ( §14.22 ) và phép gán xác định ( §16.1.1 ).

intlà nguyên thủy, biến alà một biến không đổi .

Hơn nữa, từ cùng một chương về effectively final:

Một số biến nhất định không được khai báo là cuối cùng thay vào đó được coi là cuối cùng một cách hiệu quả: ...

Vì vậy, từ cách này là worded, rõ ràng là trong ví dụ khác, akhông được coi là một biến liên tục, vì nó là không chính thức , nhưng chỉ có hiệu quả chính thức.


Hành vi

Bây giờ chúng ta đã có sự phân biệt, chúng ta hãy tìm kiếm những gì đang xảy ra và tại sao đầu ra lại khác nhau.

Bạn đang sử dụng toán tử điều kiện ? :ở đây, vì vậy chúng tôi phải kiểm tra định nghĩa của nó. Từ JLS§15.25 :

Có ba loại biểu thức điều kiện, được phân loại theo biểu thức toán hạng thứ hai và thứ ba: biểu thức điều kiện boolean , biểu thức điều kiện sốbiểu thức điều kiện tham chiếu .

Trong trường hợp này, chúng ta đang nói về một biểu thức điều kiện số , từ JLS§15.25.2 :

Loại biểu thức điều kiện số được xác định như sau:

Và đó là phần mà hai trường hợp được phân loại khác nhau.

hiệu quả cuối cùng

Phiên bản effectively finalphù hợp với quy tắc này:

Mặt khác, thăng hạng số tổng quát ( §5.6 ) được áp dụng cho toán hạng thứ hai và thứ ba, và kiểu của biểu thức điều kiện là kiểu thăng hạng của toán hạng thứ hai và thứ ba.

Đó là hành vi giống như nếu bạn sẽ làm 5 + 'd', tức là int + chardẫn đến kết quả int. Xem JLS§5.6

Quảng cáo số xác định loại quảng cáo của tất cả các biểu thức trong ngữ cảnh số. Kiểu được thăng hạng được chọn sao cho mỗi biểu thức có thể được chuyển đổi thành kiểu được thăng hạng, và trong trường hợp là phép toán số học, phép toán được xác định cho các giá trị của kiểu được thăng hạng. Thứ tự của các biểu thức trong ngữ cảnh số không có ý nghĩa đối với việc quảng bá số. Luật như sau:

[...]

Tiếp theo, chuyển đổi nguyên thủy mở rộng ( §5.1.2 ) và chuyển đổi nguyên thủy thu hẹp ( §5.1.3 ) được áp dụng cho một số biểu thức, theo các quy tắc sau:

Trong ngữ cảnh lựa chọn số, các quy tắc sau sẽ áp dụng:

Nếu bất kỳ biểu thức nào thuộc kiểu intkhông phải là một biểu thức hằng ( §15.29 ), thì kiểu được thăng cấp là intvà các biểu thức khác không thuộc kiểu sẽ inttrải qua chuyển đổi nguyên thủy mở rộng thành int.

Vì vậy, mọi thứ được quảng bá intnhư alà một intđã. Điều đó giải thích đầu ra của 97.

sau cùng

Phiên bản có finalbiến được khớp theo quy tắc này:

Nếu một trong các toán hạng là loại Tnơi Tbyte, shorthoặc char, và các toán hạng khác là một biểu thức hằng số ( §15.29 ) loại intcó giá trị được biểu diễn trong loại T, sau đó loại các biểu thức điều kiện là T.

Biến cuối cùng alà kiểu intvà một biểu thức hằng số (vì nó là final). Nó có thể đại diện được char, do đó kết quả là loại char. Điều đó kết thúc đầu ra a.


Ví dụ về chuỗi

Ví dụ với đẳng thức chuỗi dựa trên sự khác biệt cốt lõi giống nhau, finalcác biến được coi là biểu thức / biến hằng số, và effectively finalkhông phải.

Trong Java, quá trình xen chuỗi dựa trên các biểu thức hằng, do đó

"a" + "b" + "c" == "abc"

truetốt (không sử dụng cấu trúc này trong mã thực).

Xem JLS§3.10.5 :

Hơn nữa, một chuỗi ký tự luôn đề cập đến cùng một trường hợp của chuỗi lớp. Điều này là do các ký tự chuỗi - hoặc nói chung hơn , các chuỗi là giá trị của biểu thức hằng ( §15.29 ) - được "thực hiện" để chia sẻ các trường hợp duy nhất, bằng cách sử dụng phương thức String.intern( §12.5 ).

Dễ bị bỏ qua vì nó chủ yếu nói về nghĩa đen, nhưng nó thực sự cũng áp dụng cho các biểu thức hằng.


8
Vấn đề là bạn sẽ ... ? a : 'c'hoạt động giống nhau cho dù alà một biến hay một hằng số . Rõ ràng là không có gì sai với biểu thức. --- Ngược lại, a + "b" == "ab"là một biểu thức xấu , vì các chuỗi cần được so sánh bằng cách sử dụng equals()( Làm cách nào để so sánh các chuỗi trong Java? ). Thực tế là nó "vô tình" hoạt động khi alà một hằng số , chỉ là một sự pha trộn giữa các ký tự chuỗi.
Andreas

5
@Andreas Có, nhưng lưu ý rằng việc xen chuỗi là một tính năng được xác định rõ ràng của Java. Nó không phải là sự ngẫu nhiên mà có thể thay đổi vào ngày mai hoặc trong một JVM khác. "a" + "b" + "c" == "abc"phải có truetrong bất kỳ triển khai Java hợp lệ nào.
Zabuzard

10
Đúng, đó là một câu nói bâng quơ được định nghĩa rõ ràng, nhưng a + "b" == "ab"vẫn là một cách diễn đạt sai . Ngay cả khi bạn biết rằng đó alà một hằng số , nó quá dễ xảy ra lỗi khi không gọi equals(). Hoặc có thể dễ vỡ là một từ tốt hơn, tức là quá có khả năng bị phá vỡ khi mã được duy trì trong tương lai.
Andreas

2
Lưu ý rằng ngay cả trong miền chính của các biến cuối cùng có hiệu quả, tức là việc sử dụng chúng trong biểu thức lambda, sự khác biệt có thể thay đổi hành vi thời gian chạy, tức là nó có thể tạo ra sự khác biệt giữa biểu thức lambda nắm bắt và không nắm bắt, sau đó đánh giá thành một singleton , nhưng cái trước tạo ra một đối tượng mới. Nói cách khác, (final) String str = "a"; Stream.of(null, null). <Runnable>map( x -> () -> System.out.println(str)) .reduce((a,b) -> () -> System.out.println(a == b)) .ifPresent(Runnable::run);thay đổi kết quả của nó khi str(không phải) final.
Holger

7

Một khía cạnh khác là nếu biến được khai báo cuối cùng trong phần thân của phương thức, nó có hành vi khác với biến cuối cùng được truyền dưới dạng tham số.

public void testFinalParameters(final String a, final String b) {
  System.out.println(a + b == "ab");
}

...
testFinalParameters("a", "b"); // Prints false

trong khi

public void testFinalVariable() {
   final String a = "a";
   final String b = "b";
   System.out.println(a + b == "ab");  // Prints true
}

...
testFinalVariable();

nó xảy ra bởi vì trình biên dịch biết rằng việc sử dụng final String a = "a"các abiến sẽ luôn có "a"giá trị để a"a"có thể được trao đổi không có vấn đề. Ngược lại, nếu akhông được định nghĩa finalhoặc nó được định nghĩa finalnhưng giá trị của nó được gán trong thời gian chạy (như trong ví dụ ở trên với atham số cuối cùng ) thì trình biên dịch không biết gì trước khi sử dụng nó. Vì vậy, việc nối diễn ra trong thời gian chạy và một chuỗi mới được tạo ra, không sử dụng nhóm thực tập.


Về cơ bản hành vi là: nếu trình biên dịch biết rằng một biến là một hằng số có thể sử dụng nó giống như sử dụng hằng số.

Nếu biến không được xác định cuối cùng (hoặc là cuối cùng nhưng giá trị của nó được xác định trong thời gian chạy) thì không có lý do gì để trình biên dịch xử lý nó như một hằng số nếu giá trị của nó bằng một hằng số và giá trị của nó không bao giờ thay đổi.


4
Không có gì lạ ở đó :)
dbl

2
Đó là một khía cạnh khác của câu hỏi.
Davide Lorenzo MARINO

5
fineltừ khóa được áp dụng cho một tham số, có ngữ nghĩa khác với từ khóa được áp dụng cho một finalbiến cục bộ, v.v.
dbl

6
Việc sử dụng tham số là không cần thiết ở đây. Bạn chỉ có thể làm final String a; a = "a";và có được hành vi tương tự
yawkat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.