Tại sao so sánh Integer với int có thể ném NullPointerException trong Java?


81

Tôi rất bối rối khi quan sát tình huống này:

Integer i = null;
String str = null;

if (i == null) {   //Nothing happens
   ...                  
}
if (str == null) { //Nothing happens

}

if (i == 0) {  //NullPointerException
   ...
}
if (str == "0") { //Nothing happens
   ...
}

Vì vậy, như tôi nghĩ rằng hoạt động quyền anh được thực hiện đầu tiên (tức là java cố gắng trích xuất giá trị int từ null) và hoạt động so sánh có mức độ ưu tiên thấp hơn, đó là lý do tại sao ngoại lệ được ném ra.

Câu hỏi đặt ra là: tại sao nó được triển khai theo cách này trong Java? Tại sao quyền anh có mức độ ưu tiên cao hơn so với các tài liệu tham khảo? Hoặc tại sao họ không thực hiện xác minh chống lại nulltrước khi quyền anh?

Tại thời điểm này, nó trông không nhất quán khi NullPointerExceptionđược ném với các nguyên thủy được bao bọc và không được ném với các loại đối tượng thực .


Bạn sẽ nhận được một NullPointerException nếu bạn đã thực hiện str.equals ("0").
Ash Burlaczenko

Toán tử == từng được sử dụng để lưu các NPE trong bất kỳ trường hợp nào. Đối với tôi, đây chỉ là một ví dụ khác chứng tỏ ý tưởng tồi khi giới thiệu auto boxing trong Java. Nó không phù hợp vì rất nhiều lý do và không cung cấp gì trước đây. Nó chỉ làm cho mã ngắn hơn mã trong khi nó che khuất những gì đang thực sự diễn ra.
x4u

Suy nghĩ của tôi khác 180 độ. Họ không nên bao gồm các đối tượng được sử dụng ban đầu ở khắp mọi nơi. Sau đó, để trình biên dịch tối ưu hóa và sử dụng các nguyên thủy. Sau đó sẽ không có bất kỳ sự nhầm lẫn nào.
MrJacqes

Câu trả lời:


137

Câu trả lời ngắn gọn

Điểm mấu chốt là:

  • == giữa hai loại tham chiếu luôn là so sánh tham chiếu
    • Thường xuyên hơn không, ví dụ như với IntegerString, bạn muốn sử dụng equalsthay vì
  • == giữa kiểu tham chiếu và kiểu nguyên thủy số luôn là so sánh số
    • Loại tham chiếu sẽ phải chịu chuyển đổi mở hộp
    • Unboxing nullluôn némNullPointerException
  • Mặc dù Java có nhiều cách xử lý đặc biệt String, nhưng trên thực tế nó KHÔNG phải là một kiểu nguyên thủy

Các câu lệnh trên phù hợp với mọi mã Java hợp lệ nhất định . Với sự hiểu biết này, không có bất kỳ sự mâu thuẫn nào trong đoạn trích bạn đã trình bày.


Câu trả lời dài

Dưới đây là các phần JLS có liên quan:

JLS 15.21.3 Các toán tử bình đẳng tham chiếu ==!=

Nếu các toán hạng của một toán tử bình đẳng là cả hai kiểu tham chiếu hoặc kiểu null , thì thao tác này là bình đẳng đối tượng.

Điều này giải thích những điều sau:

Integer i = null;
String str = null;

if (i == null) {   // Nothing happens
}
if (str == null) { // Nothing happens
}
if (str == "0") {  // Nothing happens
}

Cả hai toán hạng đều là kiểu tham chiếu, và đó là lý do tại sao ==so sánh bình đẳng tham chiếu là.

Điều này cũng giải thích những điều sau:

System.out.println(new Integer(0) == new Integer(0)); // "false"
System.out.println("X" == "x".toUpperCase()); // "false"

Để ==trở thành bình đẳng số, ít nhất một trong các toán hạng phải là kiểu số :

JLS 15.21.1 Các toán tử bình đẳng số ==!=

Nếu các toán hạng của toán tử bình đẳng đều thuộc kiểu số hoặc một thuộc kiểu số và toán hạng kia có thể chuyển đổi thành kiểu số, thì việc thăng hạng số nhị phân được thực hiện trên các toán hạng. Nếu kiểu thăng hạng của các toán hạng là inthoặc long, thì một phép thử bình đẳng số nguyên được thực hiện; nếu kiểu được thăng hạng là float or double`, thì kiểm tra bằng dấu phẩy động sẽ được thực hiện.

Lưu ý rằng quảng cáo số nhị phân thực hiện chuyển đổi tập hợp giá trị và chuyển đổi mở hộp.

Điều này giải thích:

Integer i = null;

if (i == 0) {  //NullPointerException
}

Đây là một đoạn trích từ Phiên bản Java thứ 2 hiệu quả, Mục 49: Ưu tiên các nguyên thủy hơn so với các nguyên thủy đóng hộp :

Tóm lại, sử dụng nguyên thủy ưu tiên hơn nguyên thủy đóng hộp bất cứ khi nào bạn có lựa chọn. Các loại nguyên thủy đơn giản hơn và nhanh hơn. Nếu bạn phải sử dụng nguyên liệu đóng hộp, hãy cẩn thận! Tính năng tự động đóng hộp làm giảm tính dài dòng, nhưng không nguy hiểm, của việc sử dụng các nguyên mẫu đóng hộp. Khi chương trình của bạn so sánh hai nguyên thủy đóng hộp với ==toán tử, nó thực hiện một so sánh danh tính, điều này gần như chắc chắn không phải là điều bạn muốn. Khi chương trình của bạn thực hiện các phép tính kiểu hỗn hợp liên quan đến các nguyên thủy được đóng hộp và không được đóng hộp, nó sẽ thực hiện việc mở hộp và khi chương trình của bạn thực hiện việc mở hộp, nó có thể ném NullPointerException. Cuối cùng, khi chương trình của bạn đóng hộp các giá trị nguyên thủy, nó có thể dẫn đến việc tạo đối tượng tốn kém và không cần thiết.

Có những nơi bạn không có lựa chọn nào khác ngoài việc sử dụng nguyên liệu đóng hộp, ví dụ như thuốc chung, nhưng nếu không, bạn nên nghiêm túc xem xét nếu quyết định sử dụng nguyên liệu đóng hộp là chính đáng.

Người giới thiệu

Câu hỏi liên quan

Câu hỏi liên quan


2
Đối với lý do tại sao someRef == 0 luôn là so sánh số, đó là một lựa chọn rất đúng đắn vì so sánh các tham chiếu của hai nguyên thủy đóng hộp hầu như luôn là lỗi của lập trình viên. Sẽ là vô ích nếu mặc định là so sánh tham chiếu trong trường hợp này.
Mark Peters

2
Tại sao trình biên dịch không thay thế biểu thức (myInteger == 0)bằng (myInteger != null && myInteger == 0)thay vì dựa vào nhà phát triển để viết mã kiểm tra null bảng soạn sẵn này? IMO Tôi sẽ có thể kiểm tra if (myBoolean)và điều đó sẽ đánh giá truenếu và chỉ nếu giá trị cơ bản là cụ thể true- trước tiên tôi không cần phải kiểm tra null.
Josh M.

15

Ví dụ NPE của bạn tương đương với mã này, nhờ tính năng tự động đóng hộp :

if ( i.intValue( ) == 0 )

Do đó NPE nếu inull.


4
if (i == 0) {  //NullPointerException
   ...
}

tôi là một Số nguyên và số 0 là một số nguyên vì vậy trong những gì thực sự được thực hiện là một cái gì đó như thế này

i.intValue() == 0

Và điều này gây ra nullPointer vì i là null. Đối với Chuỗi, chúng tôi không có hoạt động này, đó là lý do tại sao không có ngoại lệ ở đó.


4

Các nhà sản xuất Java có thể đã định nghĩa ==toán tử để trực tiếp tác động lên các toán hạng của các kiểu khác nhau, trong trường hợp đó Integer I; int i;khi so sánh I==i;có thể đặt ra câu hỏi "Có Igiữ một tham chiếu đến Integergiá trị của nó ikhông?" - một câu hỏi có thể được trả lời không khó ngay cả khi Ilà null. Thật không may, Java không trực tiếp kiểm tra xem các toán hạng của các kiểu khác nhau có bằng nhau hay không; thay vào đó, nó kiểm tra xem ngôn ngữ có cho phép chuyển đổi kiểu của một trong hai toán hạng thành kiểu của toán hạng kia hay không và - nếu có - so sánh toán hạng đã chuyển đổi với toán hạng không được chuyển đổi. Hành vi như vậy có nghĩa rằng cho các biến x, yzvới một số kết hợp của các loại, nó có thể có x==yy==znhưngx!=z[ví dụ: x = 16777216f y = 16777216 z = 16777217]. Nó cũng có nghĩa là so sánh I==iđược dịch là "Chuyển đổi I thành một intvà, nếu điều đó không tạo ra ngoại lệ, hãy so sánh nó với i."


+1: Vì đã thực sự cố gắng trả lời câu hỏi của OP về "Tại sao nó lại được thiết kế như vậy?"
Martijn Courteaux

1
@MartijnCourteaux: Nhiều ngôn ngữ dường như chỉ xác định các toán tử cho các toán hạng của các kiểu so khớp và giả định rằng nếu một T có thể chuyển đổi hoàn toàn thành U, thì việc chuyển đổi ngầm đó sẽ được thực hiện mà không có khiếu nại bất kỳ lúc nào U có thể được chấp nhận nhưng T thì không. Được nó không cho hành vi như vậy, một ngôn ngữ có thể định nghĩa ==theo cách như vậy mà nếu trong tất cả các trường hợp x==y, y==zx==ztất cả các biên dịch mà không phàn nàn, ba so sánh sẽ cư xử như một quan hệ tương đương. Tò mò rằng các nhà thiết kế đẩy tất cả các loại tính năng ngôn ngữ ưa thích, nhưng bỏ qua sự tuân thủ tiên đề.
supercat

1

Đó là vì tính năng autoboxing của Javas . Trình biên dịch phát hiện, ở phía bên phải của phép so sánh bạn đang sử dụng một số nguyên nguyên thủy và cần phải bỏ hộp giá trị Integer của trình bao bọc thành một giá trị int nguyên thủy.

Vì điều đó là không thể (nó rỗng khi bạn xếp hàng) nên NullPointerExceptionsẽ bị ném.


1

Trong i == 0Java sẽ cố gắng thực hiện tự động mở hộp và thực hiện so sánh số (nghĩa là "giá trị được lưu trữ trong đối tượng trình bao bọc được tham chiếu bằng igiá trị 0nào?").

Kể từ khi inullnhững unboxing sẽ ném một NullPointerException.

Lý do diễn ra như thế này:

Câu đầu tiên của JLS § 15.21.1 Các toán tử bình đẳng số == và! = Đọc như thế này:

Nếu các toán hạng của một toán tử bình đẳng đều thuộc kiểu số hoặc một thuộc kiểu số và một toán tử có thể chuyển đổi (§5.1.8) sang kiểu số, thì việc thăng hạng số nhị phân được thực hiện trên các toán hạng (§5.6.2).

Rõ ràng ilà có thể chuyển đổi thành kiểu số và 0là kiểu số, vì vậy việc thăng hạng số nhị phân được thực hiện trên các toán hạng.

§ 5.6.2 Khuyến mãi số nhị phân cho biết (trong số những thứ khác):

Nếu bất kỳ toán hạng nào thuộc loại tham chiếu, chuyển đổi mở hộp (§5.1.8) được thực hiện.

§ 5.1.8 Chuyển đổi mở hộp cho biết (trong số những thứ khác):

Nếu r là rỗng, chuyển đổi unboxing sẽ némNullPointerException


0

Đơn giản chỉ cần viết một phương thức và gọi nó để tránh NullPointerException.

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 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.