Booleans, toán tử có điều kiện và autoboxing


132

Tại sao ném này NullPointerException

public static void main(String[] args) throws Exception {
    Boolean b = true ? returnsNull() : false; // NPE on this line.
    System.out.println(b);
}

public static Boolean returnsNull() {
    return null;
}

trong khi điều này không

public static void main(String[] args) throws Exception {
    Boolean b = true ? null : false;
    System.out.println(b); // null
}

?

Giải pháp là bằng cách thay thế falsebằng cách Boolean.FALSEtránh nullbị bỏ hộp đến boolean- điều không thể. Nhưng đó không phải là câu hỏi. Câu hỏi là tại sao ? Có bất kỳ tài liệu tham khảo nào trong JLS xác nhận hành vi này, đặc biệt là trường hợp thứ 2 không?


28
wow, autoboxing là một nguồn vô tận của ... er ... bất ngờ cho lập trình viên java, phải không? :-)
leonbloy

Tôi đã gặp một vấn đề tương tự và điều làm tôi ngạc nhiên là nó đã thất bại trên OpenJDK VM nhưng hoạt động trên HotSpot VM ... Viết một lần, chạy mọi nơi!
kodu

Câu trả lời:


92

Sự khác biệt là kiểu rõ ràng của returnsNull()phương thức ảnh hưởng đến kiểu gõ tĩnh của các biểu thức tại thời điểm biên dịch:

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)

Xem Đặc tả ngôn ngữ Java, phần 15.25 Toán tử có điều kiện? :

  • Đối với E1, các loại của toán hạng thứ 2 và thứ 3 là Booleanbooleantương ứng, vì vậy khoản này được áp dụng:

    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.

    Vì loại biểu thức là boolean, toán hạng 2 phải được ép buộc boolean. Trình biên dịch chèn mã tự động mở hộp vào toán hạng 2 (giá trị trả về của returnsNull()) để làm cho nó gõ boolean. Điều này tất nhiên gây ra NPE từ nulltrả lại tại thời gian chạy.

  • Đối với E2, các loại toán hạng thứ 2 và thứ 3 tương ứng <special null type>(không phải Booleannhư trong E1!) Và booleando đó, không áp dụng mệnh đề gõ cụ thể nào ( hãy đọc 'em! ), Vì vậy mệnh đề "khác" cuối cùng được áp dụng:

    Mặt khác, cá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).

    • S1 == <special null type>(xem §4.1 )
    • S2 == boolean
    • T1 == hộp (S1) == <special null type>(xem mục cuối cùng trong danh sách chuyển đổi quyền anh trong §5.1.7 )
    • T2 == hộp (S2) == `Boolean
    • mỡ (T1, T2) == Boolean

    Vì vậy, loại biểu thức điều kiện là Booleanvà toán hạng thứ 3 phải được ép buộc Boolean. Trình biên dịch chèn mã tự động đấm bốc cho toán hạng 3 ( false). Toán hạng thứ 2 không cần tự động hủy hộp như trong E1, do đó, không có NPE tự động hủy hộp khi nullđược trả về.


Câu hỏi này cần một phân tích loại tương tự:

Toán tử điều kiện Java ?: Kiểu kết quả


4
Làm cho ý nghĩa ... tôi nghĩ. Các §15.12.2.7 là một nỗi đau.
BalusC

Thật dễ dàng ... nhưng chỉ trong nhận thức muộn màng. :-)
Bert F

@BertF Chức năng lubtrong viết lub(T1,T2)tắt là gì?
Geek

1
@Geek - mỡ () - giới hạn trên tối thiểu - về cơ bản là siêu lớp gần nhất mà chúng có chung; vì null (loại "loại null đặc biệt") có thể được chuyển đổi hoàn toàn (mở rộng) thành bất kỳ loại nào, bạn có thể coi loại null đặc biệt là "siêu lớp" của bất kỳ loại (lớp) nào cho mục đích của ().
Bert F

25

Dòng:

    Boolean b = true ? returnsNull() : false;

được chuyển đổi nội bộ thành:

    Boolean b = true ? returnsNull().booleanValue() : false; 

để thực hiện unboxing; do đó: null.booleanValue()sẽ mang lại một NPE

Đây là một trong những cạm bẫy lớn khi sử dụng autoboxing. Hành vi này thực sự được ghi lại trong 5.1.8 JLS

Chỉnh sửa: Tôi tin rằng việc bỏ hộp là do toán tử thứ ba thuộc loại boolean, giống như (diễn viên ngầm được thêm vào):

   Boolean b = (Boolean) true ? true : false; 

2
Tại sao nó cố gắng bỏ hộp như vậy, khi giá trị cuối cùng là một đối tượng Boolean?
Erick Robertson

16

Từ Đặc tả ngôn ngữ Java, phần 15.25 :

  • 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.

Vì vậy, ví dụ cố gắng đầu tiên để gọi Boolean.booleanValue()để chuyển đổi Booleanđể booleantheo Nguyên tắc đầu tiên.

Trong trường hợp thứ hai, toán hạng thứ nhất thuộc loại null, khi thứ hai không phải là kiểu tham chiếu, do đó, chuyển đổi hộp tự động được áp dụng:

  • Mặt khác, cá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).

Điều này trả lời trường hợp đầu tiên, nhưng không phải trường hợp thứ hai.
BalusC

Có lẽ có một ngoại lệ khi một trong các giá trị là null.
Erick Robertson

@Erick: JLS có xác nhận điều này không?
BalusC

1
@Erick: Tôi không nghĩ nó có thể áp dụng được vì booleanđây không phải là loại tham chiếu.
axtavt

1
Và tôi có thể thêm ... đây là lý do tại sao bạn nên làm cho cả hai mặt của một con chim nhạn cùng loại, với các cuộc gọi rõ ràng nếu cần thiết. Ngay cả khi bạn có các thông số kỹ thuật được ghi nhớ và biết điều gì sẽ xảy ra, lập trình viên tiếp theo sẽ đọc và đọc mã của bạn có thể không. Theo ý kiến ​​khiêm tốn của tôi, sẽ tốt hơn nếu trình biên dịch chỉ tạo ra một thông báo lỗi trong những tình huống này thay vì làm những việc mà những người bình thường khó dự đoán. Chà, có thể có những trường hợp hành vi thực sự hữu ích, nhưng tôi chưa đánh được.
Jay

0

Chúng ta có thể thấy vấn đề này từ mã byte. Ở dòng 3 của mã byte chính, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()ZBoolean của giá trị null, invokevirtualphương thức java.lang.Boolean.booleanValue, nó sẽ ném NPE tất nhiên.

    public static void main(java.lang.String[]) throws java.lang.Exception;
      descriptor: ([Ljava/lang/String;)V
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=2, args_size=1
           0: invokestatic  #2                  // Method returnsNull:()Ljava/lang/Boolean;
           3: invokevirtual #3                  // Method java/lang/Boolean.booleanValue:()Z
           6: invokestatic  #4                  // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
           9: astore_1
          10: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
          13: aload_1
          14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
          17: return
        LineNumberTable:
          line 3: 0
          line 4: 10
          line 5: 17
      Exceptions:
        throws java.lang.Exception

    public static java.lang.Boolean returnsNull();
      descriptor: ()Ljava/lang/Boolean;
      flags: ACC_PUBLIC, ACC_STATIC
      Code:
        stack=1, locals=0, args_size=0
           0: aconst_null
           1: areturn
        LineNumberTable:
          line 8: 0
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.