Toán tử bậc ba của Java so với if / else trong khả năng tương thích <JDK8


113

Gần đây tôi đang đọc mã nguồn của Spring Framework. Có gì đó tôi không thể hiểu được ở đây:

public Member getMember() {
    // NOTE: no ternary expression to retain JDK <8 compatibility even when using
    // the JDK 8 compiler (potentially selecting java.lang.reflect.Executable
    // as common type, with that new base class not available on older JDKs)
    if (this.method != null) {
        return this.method;
    }
    else {
        return this.constructor;
    }
}

Phương thức này là một thành viên của lớp org.springframework.core.MethodParameter. Mã rất dễ hiểu trong khi các bình luận khó.

LƯU Ý: không có biểu thức bậc ba để duy trì khả năng tương thích JDK <8 ngay cả khi sử dụng trình biên dịch JDK 8 (có khả năng chọn java.lang.reflect.Executablelà loại phổ biến, với lớp cơ sở mới đó không có sẵn trên các JDK cũ hơn)

Sự khác biệt giữa sử dụng biểu thức bậc ba và sử dụng if...else...cấu trúc trong ngữ cảnh này là gì?

Câu trả lời:


103

Khi bạn nghĩ về loại toán hạng, vấn đề trở nên rõ ràng hơn:

this.method != null ? this.method : this.constructor

as type là kiểu chung chuyên biệt nhất của cả hai toán hạng, tức là kiểu chuyên biệt nhất chung cho cả this.methodthis.constructor.

Trong Java 7 này là java.lang.reflect.Member, tuy nhiên các thư viện lớp Java 8 giới thiệu một loại mới java.lang.reflect.Executableđược nhiều chuyên hơn generic Member. Do đó với thư viện lớp Java 8, kiểu kết quả của biểu thức bậc ba là Executablehơn Member.

Một số phiên bản (trước khi phát hành) của trình biên dịch Java 8 dường như đã tạo ra một tham chiếu rõ ràng đến Executablemã được tạo bên trong khi biên dịch toán tử bậc ba. Điều này sẽ kích hoạt tải lớp, và do đó sẽ xảy ra trong ClassNotFoundExceptionthời gian chạy khi chạy với thư viện lớp <JDK 8, bởi vì Executablechỉ tồn tại cho JDK ≥ 8.

Như Tagir Valeev đã lưu ý trong câu trả lời này , đây thực sự là một lỗi trong các phiên bản trước khi phát hành của JDK 8 và sau đó đã được sửa, vì vậy cả if-elsecách giải quyết và nhận xét giải thích đều đã lỗi thời.

Lưu ý bổ sung: Người ta có thể đi đến kết luận rằng lỗi trình biên dịch này đã xuất hiện trước Java 8. Tuy nhiên, mã byte được tạo cho bậc ba bởi OpenJDK 7 giống với mã byte được tạo bởi OpenJDK 8. Trên thực tế, kiểu của biểu thức hoàn toàn không được đề cập trong thời gian chạy, mã thực sự chỉ là kiểm tra, rẽ nhánh, tải, trả về mà không có bất kỳ kiểm tra bổ sung nào diễn ra. Vì vậy, hãy yên tâm rằng đây không phải là một vấn đề (nữa) và thực sự có vẻ như là một vấn đề tạm thời trong quá trình phát triển Java 8.


1
Sau đó, làm thế nào để mã được biên dịch bằng JDK 1.8 có thể chạy trên JDK 1.7. Tôi đã biết rằng mã được biên dịch với JDK phiên bản thấp hơn có thể chạy trên JDK phiên bản cao hơn mà không gặp sự cố.
jddxf

1
@jddxf Mọi thứ đều ổn miễn là bạn chỉ định phiên bản tệp lớp phù hợp và không sử dụng bất kỳ chức năng nào không có trong các phiên bản sau. Sự cố chắc chắn xảy ra, tuy nhiên nếu việc sử dụng như vậy diễn ra ngầm như trong trường hợp này.
dhke

13
@jddxf, sử dụng -source / -target javac options
Tagir Valeev

1
Cảm ơn tất cả các bạn, đặc biệt là dhke và Tagir Valeev, người đã đưa ra một lời giải thích thấu đáo
jddxf

30

Điều này đã được giới thiệu trong cam kết khá cũ vào ngày 3 tháng 5 năm 2013, gần một năm trước khi phát hành chính thức JDK-8. Những thời điểm đó, trình biên dịch đang được phát triển nặng, vì vậy các vấn đề tương thích như vậy có thể xảy ra. Tôi đoán, nhóm Spring vừa thử nghiệm bản dựng JDK-8 và cố gắng khắc phục các sự cố, mặc dù chúng thực sự là sự cố trình biên dịch. Bởi JDK-8 phát hành chính thức, điều này trở nên không liên quan. Bây giờ toán tử bậc ba trong mã này hoạt động tốt như mong đợi (không có tham chiếu đến Executablelớp trong tệp .class-đã biên dịch).

Hiện tại những thứ tương tự cũng xuất hiện trong JDK-9: một số mã có thể được biên dịch độc đáo trong JDK-8 bị lỗi với JDK-9 javac. Tôi đoán, hầu hết các vấn đề như vậy sẽ được khắc phục cho đến khi phát hành.


2
+1. Vì vậy, đây có phải là một lỗi trong trình biên dịch ban đầu? Hành vi đó có Executablevi phạm một số khía cạnh của thông số kỹ thuật không? Hay chỉ là Oracle nhận ra rằng họ có thể thay đổi hành vi này theo cách vẫn phù hợp với thông số kỹ thuật và không phá vỡ khả năng tương thích ngược?
ruakh

2
@ruakh, tôi đoán đó là lỗi. Trong bytecode (trong Java-8 hoặc trước đó), hoàn toàn không cần thiết phải ép kiểu rõ ràng để Executablenhập ở giữa. Trong Java-8, khái niệm suy luận kiểu biểu thức đã thay đổi đáng kể và phần này đã được viết lại hoàn toàn, do đó không có gì đáng ngạc nhiên khi các triển khai ban đầu có lỗi.
Tagir Valeev

7

Sự khác biệt chính là một if elsekhối là một câu lệnh trong khi khối ba (thường được gọi là toán tử điều kiện trong Java) là một biểu thức .

Một câu lệnh có thể thực hiện những việc như returnđối với người gọi trên một số đường dẫn điều khiển. Một biểu thức có thể được sử dụng trong một phép gán:

int n = condition ? 3 : 2;

Vì vậy, hai biểu thức trong bậc ba sau điều kiện cần phải được quy về cùng một kiểu. Điều này có thể gây ra một số hiệu ứng kỳ lạ trong Java, đặc biệt là với tính năng tự động đóng hộp và truyền tham chiếu tự động - đây là những gì nhận xét trong mã đã đăng của bạn đề cập đến. Sự ép buộc của các biểu thức trong trường hợp của bạn sẽ là một java.lang.reflect.Executablekiểu (vì đó là kiểu chuyên biệt nhất ) và kiểu đó không tồn tại trong các phiên bản Java cũ hơn.

Về mặt phong cách, bạn nên sử dụng một if elsekhối nếu mã giống như câu lệnh và một khối ba nếu nó giống như biểu thức.

Tất nhiên, bạn có thể làm cho một if elsekhối hoạt động giống như một biểu thức nếu bạn sử dụng hàm lambda.


6

Kiểu giá trị trả về trong biểu thức bậc ba bị ảnh hưởng bởi các lớp cha, lớp này đã thay đổi như được mô tả trong Java 8.

Thật khó để hiểu tại sao một dàn diễn viên không thể được viết.

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.