2011-07-05 44 views
44

Tôi biết rằng khi tôi đọc câu trả lời cho điều này tôi sẽ thấy rằng tôi đã bỏ qua một cái gì đó đó là dưới mắt tôi. Nhưng tôi đã dành 30 phút cuối cùng để cố gắng tìm ra bản thân mình mà không có kết quả.Chuyển đổi từ null thành int?

Vì vậy, tôi đã viết một chương trình trong Java 6 và phát hiện một số tính năng kỳ lạ (đối với tôi). Để thử và cách ly nó, tôi đã tạo ra hai ví dụ nhỏ. Lần đầu tiên tôi thử phương pháp sau:

private static int foo() 
{ 
    return null; 
} 

và trình biên dịch từ chối: Loại không khớp: không thể chuyển đổi từ null thành int.

Điều này là tốt với tôi và nó tôn trọng ngữ nghĩa Java mà tôi quen thuộc. Sau đó, tôi đã thử các cách sau:

private static Integer foo(int x) 
{ 
    if (x < 0) 
    { 
     return null; 
    } 
    else 
    { 
     return new Integer(x); 
    } 
} 

private static int bar(int x) 
{ 
    Integer y = foo(x); 

    return y == null ? null : y.intValue(); 
} 

private static void runTest() 
{ 
    for (int index = 2; index > -2; index--) 
    { 
     System.out.println("bar(" + index + ") = " + bar(index)); 
    } 
} 

Biên dịch này không có lỗi! Nhưng, theo ý kiến ​​của tôi, cần có một loại chuyển đổi lỗi trong dòng

return y == null ? null : y.intValue(); 

Nếu tôi chạy chương trình tôi nhận được kết quả như sau:

bar(2) = 2 
bar(1) = 1 
bar(0) = 0 
Exception in thread "main" java.lang.NullPointerException 
    at Test.bar(Test.java:23) 
    at Test.runTest(Test.java:30) 
    at Test.main(Test.java:36) 

bạn có thể giải thích hành vi này?

Cập nhật

Cảm ơn bạn rất nhiều vì nhiều câu trả lời làm rõ. Tôi hơi lo lắng vì ví dụ này không tương ứng với trực giác của tôi. Một điều làm tôi băn khoăn là rằng một null đã được chuyển đổi thành một int và tôi đã tự hỏi kết quả sẽ là gì là: 0 như trong C++? Điều đó sẽ rất lạ. Tốt rằng việc chuyển đổi là không thể khi chạy (ngoại lệ con trỏ null).

Trả lời

54

Hãy nhìn vào dòng:

return y == null ? null : y.intValue(); 

Trong một tuyên bố ? :, cả hai mặt của : phải có cùng loại. Trong trường hợp này, Java sẽ làm cho nó có loại Integer. An Integer có thể là null, vì vậy phía bên trái là ok. Biểu thức y.intValue() thuộc loại int, nhưng Java sẽ tự động chuyển hộp này thành Integer (lưu ý, bạn cũng có thể viết y mà có thể đã lưu bạn hộp này).

Bây giờ, kết quả phải được unboxed một lần nữa để int, bởi vì kiểu trả về của phương thức là int.Nếu bạn mở hộp số Integernull, bạn sẽ nhận được NullPointerException.

Lưu ý: Paragraph 15.25 Đặc tả ngôn ngữ Java giải thích các quy tắc chính xác cho các loại chuyển đổi liên quan đến toán tử điều kiện ? :.

+0

Quy tắc chung về cách trình biên dịch cố gắng giải quyết các loại trong biểu thức bậc ba là gì? –

+2

+1, điều này có ý nghĩa bây giờ, đối với tôi. Tại sao trình biên dịch không cho phép thử nghiệm đầu tiên, 'int foo() {return null; } ', như trong trường hợp này một lần nữa, nó sẽ tự động' null' thành 'Integer'? hoặc, tôi đoán câu hỏi của tôi nói cách khác là, tại sao nó đã chọn để autobox các lĩnh vực trong hoạt động ternary để 'Integer' và không để chúng như là nguyên thủy? Quyết định đó dựa trên đâu? – c00kiemon5ter

+0

@Oli Tôi không chắc chắn nhưng nó có thể có bahaviour đáng ngạc nhiên, trong đó đây là một ví dụ. Xem [đoạn 15.25] (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25) của JLS. – Jesper

1

Sự cố với tự động đóng hộp null giá trị có thể thực sự gây phiền toái. Trong ví dụ của bạn, đó là sự kết hợp của kiểu kết quả toán tử thứ cấp suy ra và tự động tạo hộp thư (JLS nên được tham khảo tại sao nó hoạt động như thế)

Nhưng nói chung, bạn nên tránh sử dụng các loại trình bao bọc. Sử dụng int thay vì Integer. Nếu bạn cần một giá trị đặc biệt có nghĩa là "không có kết quả", thì bạn có thể sử dụng ví dụ Integer.MAX_VALUE.

+0

Đó là lý do tại sao tôi có kiểu int trả về. Các null đến từ một bản sao và dán và khi tôi thấy rằng trình biên dịch đã không phàn nàn tôi đã khá ngạc nhiên. – Giorgio

6

Loại kiểu trả về được suy ra bởi Java tại đây. Đó là vấn đề ..

http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#15.25

Đây là vấn đề thực tế -

Nếu một trong những toán hạng thứ hai và thứ ba là các loại rỗng và loại kia là một loại tài liệu tham khảo , sau đó loại biểu thức có điều kiện là loại tham chiếu đó.

Vì vậy, về cơ bản trình biên dịch suy luận kiểu trả về của biểu thức điều kiện là Integer và thats lý do tại sao nó cho phép bạn biên dịch thành công.

EDIT: Xem quy tắc trong ý kiến ​​

+1

@Kai: Bây giờ tôi bắt đầu hiểu: trình biên dịch xác định loại kết quả của? toán tử đầu tiên và tìm Integer. Chỉ sau đó, nó cố gắng để phù hợp với Integer với kiểu trả về int. Chúng tương thích với điều kiện unboxing được thực hiện. Nó không bao giờ thấy rằng null không thể được unboxed để int. Trong ví dụ đầu tiên chỉ có một bước chuyển đổi, đó là lý do tại sao trình biên dịch cho biết các kiểu không tương thích. – Giorgio

+0

@Giorgio - vâng. Nếu bạn nhìn vào liên kết, có thêm làm rõ --- Nó hộp cả toán hạng và sau đó áp dụng cùng một logic (trong trường hợp này, int được đóng hộp để Integer) .. kiểu trả về được giả định là Integer. ** Nếu không, toán hạng thứ hai và thứ ba là các loại S1 và S2 tương ứng. Gọi T1 là kiểu kết quả từ việc áp dụng chuyển đổi quyền anh sang 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 có điều kiện là kết quả của việc áp dụng chuyển đổi bắt giữ (§5.10.10) đến lub (T1, T2) (§15.12.2.7). – Kal

+0

"* và loại kia là kiểu tham chiếu *" <--- int không phải là tham chiếu, vì vậy không đáp ứng đoạn này. – edutesoy

3

này minh họa một sự khác biệt có vấn đề giữa đường một nhân đọc mã và một trình biên dịch đọc mã.

Khi bạn thấy một biểu ternary, nó rất có thể cho bạn tinh thần chia nó thành hai phần, theo phong cách của một câu lệnh if/else:

if (y == null) 
    return null; 
else 
    return y.intValue(); 

Bạn có thể thấy rằng đây không hợp lệ, vì nó dẫn đến một nhánh có thể trong đó một phương thức được xác định để trả về một int thực sự trở về null (bất hợp pháp!).

Trình biên dịch xem là biểu thức , phải có loại. Nó lưu ý rằng hoạt động ba năm bao gồm một số null ở một bên và một mặt khác là int; do hành vi autoboxing của Java, nó xuất hiện với một "phỏng đoán tốt nhất" (thuật ngữ của tôi, không phải Java) như kiểu của biểu thức là: Integer (điều này là công bằng: đó là loại duy nhất hợp pháp có thể là null hoặc một hộp int).

Vì phương pháp được cho là trả lại một int, điều này là tốt từ quan điểm của trình biên dịch: biểu thức được trả về đánh giá là Integer, có thể được tự động đóng hộp.

1

này biên dịch

private static int foo() 
{ 
    return (Integer)null; 
} 
+2

Chắc chắn nó biên dịch nhưng u sẽ nhận được một lỗi Runtime (NullPointer) vì Integer.intValue() không thể được gọi. – Orri

12

Guava có một giải pháp khá thanh lịch cho này bằng MoreObjects.firstNonNull:

Integer someNullInt = null; 
int myInt = MoreObjects.firstNonNull(someNullInt, 0); 
+0

Muốn gọi phương thức này trên đối tượng Ints. Liên kết – sandrozbinden

+0

là ký tự đã chết – Blauhirn

3

Chỉ trong trường hợp bạn không có ổi trong dự án của bạn, nhưng đã sử dụng Apache Commons, bạn có thể sử dụng Apache Lang3 với lớp học ObjectUtils của mình.

Việc sử dụng về cơ bản là giống như ổi:

Integer number = null; 
int notNull = ObjectUtils.firstNonNull(number, 0); 

Lưu ý, rằng phương pháp này trong thư viện ổi hoạt động nhanh hơn, so với Apache. Dưới đây là một so sánh ngắn tôi chỉ được thực hiện trên máy tính xách tay của tôi (Core i7-7500U 2,7 GHz), Oracle Java 8, nhiều lần chạy, JVM làm nóng trước, kết quả được tính trung bình:

╔══════════════╦══════╦══════╦════════╦══════╗ 
║ Library/Runs ║ 1000 ║ 1mln ║ 100mln ║ 1bln ║ 
╠══════════════╬══════╬══════╬════════╬══════╣ 
║ Apache  ║ 1 ║ 30 ║ 782 ║ 9981 ║ 
║ Guava  ║ 1 ║ 22 ║ 120 ║ 828 ║ 
╚══════════════╩══════╩══════╩════════╩══════╝ 

Kết quả có trong mili giây. Tôi không nghĩ rằng bạn thường cần chạy phương pháp này hàng tỷ lần, nhưng vẫn luôn tốt khi so sánh hiệu suất

Các vấn đề liên quan