Trả về null dưới dạng int được phép với toán tử ternary nhưng không phải là câu lệnh if


186

Hãy xem mã Java đơn giản trong đoạn mã sau:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

Trong mã Java đơn giản nhất này, temp()phương thức không gặp lỗi trình biên dịch mặc dù kiểu trả về của hàm là intvà chúng tôi đang cố gắng trả về giá trị null(thông qua câu lệnh return true ? null : 0;). Khi được biên dịch, điều này rõ ràng gây ra ngoại lệ thời gian chạy NullPointerException.

Tuy nhiên, có vẻ như điều tương tự là sai nếu chúng ta biểu diễn toán tử ternary bằng một ifcâu lệnh (như trong same()phương thức), điều này gây ra lỗi thời gian biên dịch! Tại sao?


6
Ngoài ra, int foo = (true ? null : 0)new Integer(null)cả hai đều biên dịch tốt, thứ hai là hình thức tự động rõ ràng.
Izkata

2
@Izkata vấn đề ở đây là để tôi hiểu lý do tại sao trình biên dịch lại cố gắng tự động chuyển nullsang Integer... Điều đó sẽ giống như "đoán" với tôi hoặc "làm mọi thứ hoạt động" ...
Marsellus Wallace

1
... Huhm, tôi nghĩ rằng tôi đã có câu trả lời ở đó, vì hàm tạo Integer (những gì tài liệu tôi tìm thấy được sử dụng cho autoboxing) được phép lấy Chuỗi làm đối số (có thể là null). Tuy nhiên, họ cũng nói rằng hàm tạo hoạt động giống hệt với phương thức parseInt (), sẽ ném NumberFormatException sau khi được thông qua null ...
Izkata

3
@Izkata - c'tor đối số String cho Integer không phải là một oepration tự động. Chuỗi không thể được tự động chuyển sang Số nguyên. (Hàm Integer foo() { return "1"; }sẽ không biên dịch.)
Ted Hopp

5
Thật tuyệt, đã học được điều gì đó mới về người điều khiển chim nhạn!
oksayt

Câu trả lời:


118

Trình biên dịch diễn giải nullnhư một tham chiếu null cho một Integer, áp dụng các quy tắc tự động mở hộp / hủy hộp cho toán tử điều kiện (như được mô tả trong Đặc tả ngôn ngữ Java, 15.25 ) và vui vẻ di chuyển. Điều này sẽ tạo ra một NullPointerExceptionthời gian chạy, mà bạn có thể xác nhận bằng cách thử nó.


Đưa ra liên kết đến Đặc tả ngôn ngữ Java mà bạn đã đăng, bạn nghĩ điểm nào sẽ được thực thi trong trường hợp câu hỏi ở trên? Cái cuối cùng (vì tôi vẫn đang cố hiểu capture conversionlub(T1,T2)) ?? Ngoài ra, có thực sự có thể áp dụng quyền anh với giá trị null không? Điều này sẽ không giống như "đoán" ??
Marsellus Wallace

Ỏ @ Gevorg Một con trỏ null là một con trỏ hợp lệ cho mọi đối tượng có thể để không có gì xấu có thể xảy ra ở đó. Trình biên dịch chỉ giả định rằng null là một Integer mà sau đó nó có thể tự động hộp đến int.
Voo

1
@Gevorg - Xem bình luận của nowaq và phản hồi của tôi về bài đăng của anh ấy. Tôi nghĩ rằng anh ấy đã chọn đúng mệnh đề. lub(T1,T2)là loại tham chiếu cụ thể phổ biến nhất trong hệ thống phân cấp loại của T1 và T2. (Cả hai đều chia sẻ ít nhất Object, vì vậy luôn có một loại tham chiếu cụ thể nhất.)
Ted Hopp

8
@Gevorg - nullkhông được đóng hộp vào Số nguyên, nó được hiểu là một tham chiếu đến một Số nguyên (một tham chiếu null, nhưng đó không phải là vấn đề). Không có đối tượng Integer nào được xây dựng từ null, vì vậy không có lý do nào cho NumberFormatException.
Ted Hopp

1
@Gevorg - Nếu bạn xem các quy tắc chuyển đổi quyền anh và áp dụng chúng cho null(không phải là loại số nguyên thủy), mệnh đề áp dụng là "Nếu p là giá trị của bất kỳ loại nào khác, chuyển đổi quyền anh tương đương với chuyển đổi nhận dạng ". Vì vậy, quyền anh chuyển đổi nullthành Integernăng suất null, mà không cần gọi bất kỳ nhà Integerxây dựng nào .
Ted Hopp

40

Tôi nghĩ rằng, trình biên dịch Java diễn giải true ? null : 0như một Integerbiểu thức, có thể được chuyển đổi hoàn toàn thành int, có thể cho NullPointerException.

Đối với trường hợp thứ hai, khái niệm nulllà các đặc biệt loại rỗng see , vì vậy mã return nulltạo ra loại phù hợp.


2
Tôi cho rằng điều này có liên quan đến tự động đấm bốc? Có lẽ lợi nhuận đầu tiên sẽ không được biên dịch trước Java 5, phải không?
Michael McGowan

@Michael dường như là trường hợp nếu bạn đặt mức tuân thủ của Eclipse là trước 5.
Jonathon Faust

@Michael: điều này chắc chắn trông giống như tự động đấm bốc (Tôi khá mới đối với Java và không thể đưa ra tuyên bố rõ ràng hơn - xin lỗi).
Vlad

1
@Vlad trình biên dịch sẽ kết thúc true ? null : 0như Integerthế nào? Bằng cách tự động hộp 0đầu tiên ??
Marsellus Wallace

1
@Gevorg: Xem tại đây : Mặt khác, toán hạng thứ hai và thứ ba có kiểu S1 và S2 tương ứng. Đặt T1 là loại kết quả từ việc áp dụng chuyển đổi quyền anh thành S1 và để T2 là loại kết quả từ việc áp dụng chuyển đổi quyền anh sang S2. và văn bản sau.
Vlad

32

Trên thực tế, tất cả được giải thích trong Đặc tả ngôn ngữ Java .

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

  • Nếu các toán hạng thứ hai và thứ ba có cùng loại (có thể là loại null), thì đó là loại của biểu thức điều kiện.

Do đó, "null" trong bạn (true ? null : 0)nhận được một kiểu int và sau đó được tự động chuyển sang Integer.

Hãy thử một cái gì đó như thế này để xác minh điều này (true ? null : null)và bạn sẽ nhận được lỗi trình biên dịch.


3
Nhưng mệnh đề đó của các quy tắc không được áp dụng: toán hạng thứ hai và thứ ba không có cùng loại.
Ted Hopp

1
Sau đó, câu trả lời dường như nằm trong câu lệnh sau:> Mặt khác, toán hạng thứ hai và thứ ba có kiểu S1 và S2 tương ứng. Đặt T1 là loại kết quả từ việc áp dụng chuyển đổi quyền anh thành S1 và để T2 là loại kết quả từ việc áp dụng chuyển đổi quyền anh sang S2. Loại biểu thức điều kiện là kết quả của việc áp dụng chuyển đổi chụp (§5.1.10) sang mỡ (T1, T2) (§15.12.2.7).
nowaq

Tôi nghĩ đó là điều khoản áp dụng. Sau đó, nó cố gắng áp dụng tự động hủy hộp để trả về một intgiá trị từ hàm, điều này gây ra NPE.
Ted Hopp

@nowaq Mình cũng nghĩ thế. Tuy nhiên, nếu bạn cố gắng một cách rõ ràng hộp nullđể Integervới new Integer(null);"Hãy T1 là loại mà kết quả từ việc áp dụng chuyển đổi đấm bốc để S1 ..." bạn sẽ nhận được một NumberFormatExceptionvà đây không phải là trường hợp ...
Marsellus Wallace

@Gevorg Tôi nghĩ rằng vì một ngoại lệ xảy ra khi tập quyền anh, chúng tôi không nhận được bất kỳ kết quả nào ở đây. Trình biên dịch chỉ bắt buộc phải tạo mã theo định nghĩa mà nó thực hiện - chúng ta chỉ cần có ngoại lệ trước khi hoàn thành.
Voo

25

Trong trường hợp của ifcâu lệnh, nulltham chiếu không được coi là Integertham chiếu vì nó không tham gia vào một biểu thức buộc nó phải được giải thích như vậy. Do đó, lỗi có thể dễ dàng bắt gặp tại thời gian biên dịch vì rõ ràng đó là lỗi loại .

Đối với toán tử có điều kiện, Đặc tả ngôn ngữ Java §15.25 Toán tử điều kiện có điều kiện ? :Trả lời điều này độc đáo trong các quy tắc về cách áp dụng chuyển đổi loại:

  • Nếu các toán hạng thứ hai và thứ ba có cùng loại (có thể là loại null), thì đó là loại của biểu thức điều kiện.

    Không áp dụng vì nullkhông int.

  • Nếu một trong các toán hạng thứ hai và thứ ba thuộc kiểu boolean và kiểu khác là kiểu Boolean, thì kiểu của biểu thức điều kiện là boolean.

    Không áp dụng vì cũng không nullphải intbooleanhoặc Boolean.

  • Nếu một trong các toán hạng thứ hai và thứ ba thuộc loại null và loại khác là loại tham chiếu, thì loại biểu thức điều kiện là loại tham chiếu đó.

    Không áp dụng vì nullthuộc loại null, nhưng intkhông phải là loại tham chiếu.

  • Mặt khác, nếu các toán hạng thứ hai và thứ ba có các loại có thể chuyển đổi (§5.1.8) thành các loại số, thì có một số trường hợp: [Khác]

    Áp dụng: nullđược coi là có thể chuyển đổi thành loại số và được định nghĩa trong §5.1. Chuyển đổi 8 hộp Unboxing Chuyển đổi để ném a NullPointerException.

Nếu 0được autoboxed Integerthì trình biên dịch đang thực thi trường hợp cuối cùng của "quy tắc toán tử ternary" như được mô tả trong Đặc tả ngôn ngữ Java. Nếu đó là sự thật, thì thật khó để tôi tin rằng nó sẽ chuyển sang trường hợp 3 của cùng một quy tắc có null và loại tham chiếu làm cho giá trị trả về của toán tử ternary là loại tham chiếu (Integer) .. .
Marsellus Wallace

@Gevorg - Tại sao khó có thể tin rằng nhà điều hành ternary đang trả lại Integer? Đó chính xác là những gì đang xảy ra; NPE đang được tạo bằng cách cố gắng bỏ hộp giá trị biểu thức để trả về một inthàm từ hàm. Thay đổi chức năng để trả về một Integervà nó sẽ trở lại nullmà không có vấn đề.
Ted Hopp

2
@TedHopp: Gevorg đã trả lời bản sửa đổi trước đó của câu trả lời của tôi, điều này không đúng. Bạn nên bỏ qua sự khác biệt.
Jon Purdy

@JonPurdy "Một loại được cho là có thể chuyển đổi thành loại số nếu đó là loại số hoặc là loại tham chiếu có thể được chuyển đổi thành loại số bằng cách chuyển đổi hộp thư" và tôi không nghĩ rằng nó nullthuộc loại này . Ngoài ra, sau đó chúng tôi sẽ chuyển sang bước "Khác, khuyến mãi số nhị phân (§5.6.2) được áp dụng ... Lưu ý rằng quảng cáo số nhị phân thực hiện chuyển đổi unboxing (§5.1.8) ..." để xác định loại trả về. Nhưng chuyển đổi unboxing sẽ tạo ra một NPE và điều này chỉ xảy ra trong thời gian chạy chứ không phải trong khi cố gắng xác định loại toán tử ternary. Tôi vẫn còn bối rối ..
Marsellus Wallace

@Gevorg: Unboxing xảy ra trong thời gian chạy. Cái nullđược coi như thể nó có loại int, nhưng thực sự tương đương với throw new NullPointerException(), đó là tất cả.
Jon Purdy

11

Điều đầu tiên cần ghi nhớ là các toán tử ternary Java có "kiểu" và đây là điều mà trình biên dịch sẽ xác định và xem xét bất kể loại thực tế / thực của tham số thứ hai hoặc thứ ba là gì. Tùy thuộc vào một số yếu tố, loại toán tử ternary được xác định theo các cách khác nhau như được minh họa trong Đặc tả ngôn ngữ Java 15,26

Trong câu hỏi trên chúng ta nên xem xét trường hợp cuối cùng:

Mặt khác, các toán hạng thứ hai và thứ ba có kiểu S1S2 tương ứng. Đặt T1 là loại kết quả từ việc áp dụng chuyển đổi quyền anh thành S1 và để T2 là loại kết quả từ việc áp dụng chuyển đổi quyền anh sang S2 . Loại biểu thức điều kiện là kết quả của việc áp dụng chuyển đổi chụp (§5.1.10) sang mỡ (T1, T2) (§15.12.2.7).

Đây là trường hợp phức tạp nhất một khi bạn xem xét áp dụng chuyển đổi chụp (§5.1.10) và hầu hết tất cả tại Lub (T1, T2) .

Bằng tiếng Anh đơn giản và sau khi đơn giản hóa cực độ, chúng ta có thể mô tả quá trình tính toán "Siêu lớp chung nhỏ nhất" (vâng, nghĩ về LCM) của các tham số thứ hai và thứ ba. Điều này sẽ cung cấp cho chúng tôi "loại" toán tử ternary. Một lần nữa, những gì tôi vừa nói là một sự đơn giản hóa cực độ (xem xét các lớp thực hiện nhiều giao diện phổ biến).

Ví dụ: nếu bạn thử như sau:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

Bạn sẽ nhận thấy rằng loại kết quả của biểu thức điều kiện là java.util.Datevì nó là "Siêu lớp chung tối thiểu" cho cặp Timestamp/ Time.

nullcó thể được hộp tự động cho bất cứ thứ gì, "Siêu lớp chung nhỏ nhất" là Integerlớp và đây sẽ là kiểu trả về của biểu thức điều kiện (toán tử ternary) ở trên. Giá trị trả về sau đó sẽ là một con trỏ rỗng loại Integervà đó là giá trị sẽ được trả về bởi toán tử ternary.

Trong thời gian chạy, khi Máy ảo Java bỏ hộp thì Integera NullPointerExceptionbị ném. Điều này xảy ra bởi vì JVM cố gắng gọi hàm null.intValue(), nullkết quả của việc tự động đóng hộp.

Theo ý kiến ​​của tôi (và vì ý kiến ​​của tôi không nằm trong Đặc tả ngôn ngữ Java, nhiều người sẽ thấy nó không đúng), trình biên dịch thực hiện công việc kém trong việc đánh giá biểu thức trong câu hỏi của bạn. Cho rằng bạn đã viết true ? param1 : param2trình biên dịch nên xác định ngay rằng tham số đầu tiên - null- sẽ được trả về và nó sẽ tạo ra lỗi trình biên dịch. Điều này hơi giống với khi bạn viết while(true){} etc...và trình biên dịch phàn nàn về mã bên dưới vòng lặp và gắn cờ với nó Unreachable Statements.

Trường hợp thứ hai của bạn khá đơn giản và câu trả lời này đã quá dài ...;)

ĐIỀU CHỈNH:

Sau một phân tích khác, tôi tin rằng tôi đã sai khi nói rằng một nullgiá trị có thể được đóng hộp / tự động đóng hộp vào bất cứ thứ gì. Nói về lớp Integer, quyền anh rõ ràng bao gồm việc gọi hàm new Integer(...)tạo hoặc có thể Integer.valueOf(int i);(tôi tìm thấy phiên bản này ở đâu đó). Cái trước sẽ ném một NumberFormatException(và điều này không xảy ra) trong khi cái thứ hai sẽ không có ý nghĩa vì intkhông thể null...


1
Các nulltrong mã gốc OP của không được đóng hộp. Cách thức hoạt động của nó là: trình biên dịch giả định rằng đó nulllà một tham chiếu đến một Integer. Sử dụng các quy tắc cho các loại biểu thức ternary, nó quyết định toàn bộ biểu thức là một biểu thức Integer. Sau đó, nó tạo mã để tự động hộp 1(trong trường hợp điều kiện ước tính false). Trong khi thực hiện, điều kiện ước tính trueđể biểu thức ước tính null. Khi cố gắng trả về một inttừ hàm, nullhộp được bỏ hộp. Điều đó sau đó ném một NPE. (Trình biên dịch có thể tối ưu hóa hầu hết những thứ này.)
Ted Hopp

4

Trên thực tế, trong trường hợp đầu tiên, biểu thức có thể được ước tính, vì trình biên dịch biết, nó phải được đánh giá là một Integer, tuy nhiên trong trường hợp thứ hai, loại giá trị trả về ( null) không thể được xác định, do đó không thể biên dịch được. Nếu bạn truyền nó tới Integer, mã sẽ biên dịch.


2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}

0

Còn cái này thì sao:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

Đầu ra là đúng, đúng.

Màu Eclipse mã 1 trong biểu thức điều kiện là hộp tự động.

Tôi đoán là trình biên dịch đang thấy kiểu trả về của biểu thức là Object.

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.