Warum kann der Vergleich von Integer mit int NullPointerException in Java auslösen?

81

Es war sehr verwirrend für mich, diese Situation zu beobachten:

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

Da ich denke, dass die Boxoperation zuerst ausgeführt wird (dh Java versucht, den int-Wert zu extrahieren null) und die Vergleichsoperation eine niedrigere Priorität hat, wird die Ausnahme ausgelöst.

Die Frage ist: Warum ist es so in Java implementiert? Warum hat das Boxen eine höhere Priorität als das Vergleichen von Referenzen? Oder warum haben sie nullvor dem Boxen keine Überprüfung durchgeführt ?

Im Moment sieht es inkonsistent aus, wenn NullPointerExceptiones mit umschlossenen Grundelementen und nicht mit echten Objekttypen geworfen wird .

römisch
quelle
Sie würden eine NullPointerException erhalten, wenn Sie str.equals ("0") ausführen würden.
Ash Burlaczenko
Der Operator == war früher unter keinen Umständen gegen NPEs gespeichert. Für mich ist dies nur ein weiteres Beispiel, das zeigt, was für eine schlechte Idee es war, das automatische Boxen in Java einzuführen. Es passt einfach aus so vielen Gründen nicht dazu und bietet nichts, was vorher nicht da war. Es macht den Code nur kürzer, während es verdeckt, was wirklich vor sich geht.
x4u
Meine Gedanken sind um 180 Grad unterschiedlich. Sie sollten nicht überall die verwendeten Grundelemente enthalten. Lassen Sie dann den Compiler die Grundelemente optimieren und verwenden. Dann würde es keine Verwirrung geben.
MrJacqes

Antworten:

137

Die kurze Antwort

Der entscheidende Punkt ist folgender:

  • == zwischen zwei Referenztypen ist immer Referenzvergleich
    • Meistens, z. B. mit Integerund String, möchten Sie equalsstattdessen verwenden
  • == zwischen einem Referenztyp und einem numerischen Grundtyp ist immer ein numerischer Vergleich
    • Der Referenztyp wird einer Unboxing-Konvertierung unterzogen
    • Unboxing nullwirft immerNullPointerException
  • Während Java viele spezielle Behandlungen für hat String, ist es in der Tat KEIN primitiver Typ

Die obigen Anweisungen gelten für jeden gültigen Java-Code. Mit diesem Verständnis gibt es keinerlei Inkonsistenz in dem von Ihnen präsentierten Snippet.


Die lange Antwort

Hier sind die relevanten JLS-Abschnitte:

JLS 15.21.3 Referenzgleichheitsoperatoren ==und!=

Wenn die Operanden eines Gleichheitsoperators entweder vom Referenztyp oder vom Nulltyp sind , ist die Operation Objektgleichheit.

Dies erklärt Folgendes:

Integer i = null;
String str = null;

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

Beide Operanden sind Referenztypen, und deshalb ==ist dies der Vergleich der Referenzgleichheit.

Dies erklärt auch Folgendes:

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

Um ==numerische Gleichheit zu erreichen, muss mindestens einer der Operanden ein numerischer Typ sein :

JLS 15.21.1 Numerische Gleichheitsoperatoren ==und!=

Wenn die Operanden eines Gleichheitsoperators beide vom numerischen Typ sind oder einer vom numerischen Typ und der andere vom numerischen Typ konvertierbar ist, wird für die Operanden eine binäre numerische Heraufstufung durchgeführt. Wenn der heraufgestufte Typ der Operanden intoder ist long, wird ein ganzzahliger Gleichheitstest durchgeführt; Wenn der float or heraufgestufte Typ double` ist, wird ein Gleitkomma-Gleichheitstest durchgeführt.

Beachten Sie, dass die binäre numerische Heraufstufung eine Konvertierung von Wertesätzen und eine Unboxing-Konvertierung durchführt.

Dies erklärt:

Integer i = null;

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

Hier ist ein Auszug aus Effective Java 2nd Edition, Punkt 49: Ziehen Sie Grundelemente Boxed-Grundelementen vor :

Zusammenfassend lässt sich sagen, dass Sie Primitive gegenüber Boxed-Primitiven bevorzugen, wenn Sie die Wahl haben. Primitive Typen sind einfacher und schneller. Wenn Sie Grundelemente in Schachteln verwenden müssen, seien Sie vorsichtig! Autoboxing reduziert die Ausführlichkeit, aber nicht die Gefahr der Verwendung von Grundelementen in Schachteln. Wenn Ihr Programm zwei Grundelemente mit dem ==Operator vergleicht, führt es einen Identitätsvergleich durch, der mit ziemlicher Sicherheit nicht Ihren Wünschen entspricht. Wenn Ihr Programm Berechnungen vom gemischten Typ mit primitiven Box- und Unboxed-Grundelementen ausführt, wird das Unboxing ausgeführt, und wenn Ihr Programm das Unboxing ausführt, kann es ausgelöst werden NullPointerException. Wenn Ihr Programm primitive Werte enthält, kann dies zu kostspieligen und unnötigen Objekterstellungen führen.

Es gibt Orte, an denen Sie keine andere Wahl haben, als Box-Primitive zu verwenden, z. B. Generika. Andernfalls sollten Sie ernsthaft überlegen, ob eine Entscheidung zur Verwendung von Box-Primitiven gerechtfertigt ist.

Verweise

Verwandte Fragen

Verwandte Fragen

Polygenschmierstoffe
quelle
2
Das Warum someRef == 0 ist immer ein numerischer Vergleich, es ist eine sehr gute Wahl, da der Vergleich der Referenzen von zwei Grundelementen in Kästchen fast immer ein Programmiererfehler ist. In diesem Fall wäre es sinnlos, standardmäßig auf Vergleiche zu verweisen.
Mark Peters
2
Warum sollte nicht der Compiler ersetzen Sie den Ausdruck (myInteger == 0)mit sich (myInteger != null && myInteger == 0)stattdessen auf den Entwickler zu verlassen , diese vorformulierten null-checking Code zu schreiben? IMO Ich sollte in der Lage sein zu überprüfen if (myBoolean)und das sollte truegenau dann ausgewertet werden, wenn der zugrunde liegende Wert spezifisch ist true- ich sollte nicht zuerst null prüfen müssen.
Josh M.
15

Ihr NPE-Beispiel entspricht diesem Code dank Autoboxing :

if ( i.intValue( ) == 0 )

Daher NPE wenn iist null.

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

Ich bin eine ganze Zahl und die 0 ist eine int. In dem, was wirklich getan wird, ist so etwas

i.intValue() == 0

Und dies verursacht den nullPointer, weil das i null ist. Für String haben wir diese Operation nicht, deshalb gibt es dort keine Ausnahme.

Damian Leszczyński - Vash
quelle
4

Die Hersteller von Java hätten den ==Operator so definieren können, dass er direkt auf Operanden unterschiedlicher Typen einwirkt. In diesem Fall könnte Integer I; int i;der Vergleich I==i;die Frage stellen: "Enthält Iein Verweis auf einen IntegerWert, dessen Wert ist i?" - eine Frage, die ohne Schwierigkeiten beantwortet werden kann auch wenn Inull ist. Leider prüft Java nicht direkt, ob Operanden unterschiedlicher Typen gleich sind. Stattdessen wird geprüft, ob die Sprache die Konvertierung des Typs eines Operanden in den Typ des anderen zulässt, und - falls dies der Fall ist - der konvertierte Operand mit dem nicht konvertierten verglichen. Ein solches Verhalten bedeutet , dass für Variablen x, yund zmit einigen Kombinationen von Arten, ist es möglich zu haben x==yund y==zaberx!=z[zB x = 16777216f y = 16777216 z = 16777217]. Dies bedeutet auch, dass der Vergleich I==iübersetzt wird als "I in ein konvertieren intund, wenn dies keine Ausnahme auslöst, mit i" vergleichen ".

Superkatze
quelle
+1: Für den tatsächlichen Versuch, die Frage des OP zu beantworten: "Warum ist das so konzipiert?"
Martijn Courteaux
1
@MartijnCourteaux: Viele Sprachen scheinen Operatoren nur für Operanden mit übereinstimmenden Typen zu definieren und gehen davon aus, dass eine implizite Konvertierung eines T immer dann ohne Beanstandung durchgeführt werden sollte, wenn ein U akzeptiert werden könnte, ein T jedoch nicht. Wäre es nicht für ein solches Verhalten, eine Sprache definieren könnte ==in der Weise , dass in allen Fällen , wenn , wo x==y, y==zund x==zalle Kompilierung ohne Beschwerde, die drei Vergleiche als Äquivalenzrelation verhält. Neugierig, dass Designer alle möglichen ausgefallenen Sprachfunktionen nutzen, aber die axiomatische Konformität ignorieren.
Supercat
1

Es ist wegen Javas Autoboxing- Funktion. Der Compiler erkennt, dass Sie auf der rechten Seite des Vergleichs eine primitive Ganzzahl verwenden und den Wrapper-Integer-Wert ebenfalls in einen primitiven int-Wert entpacken müssen.

Da dies nicht möglich ist (es ist null, wie Sie dargelegt haben), NullPointerExceptionwird das geworfen.

perdian
quelle
1

In i == 0Java wird versucht, das automatische Entpacken durchzuführen und einen numerischen Vergleich durchzuführen (dh "Wird der im Wrapper-Objekt gespeicherte Wert durch idenselben Wert wie der Wert 0?").

Da iwird nulldas Unboxing ein werfen NullPointerException.

Die Argumentation lautet wie folgt:

Der erste Satz von JLS § 15.21.1 Numerische Gleichheitsoperatoren == und! = Lautet wie folgt :

Wenn die Operanden eines Gleichheitsoperators beide vom numerischen Typ sind oder einer vom numerischen Typ und der andere vom numerischen Typ konvertierbar ist (§5.1.8), wird für die Operanden eine binäre numerische Heraufstufung durchgeführt (§5.6.2).

Es iist klar, dass es in einen numerischen Typ konvertierbar ist und 0ein numerischer Typ ist, sodass die binäre numerische Heraufstufung für die Operanden durchgeführt wird.

§ 5.6.2 Binäre numerische Werbung sagt (unter anderem):

Wenn einer der Operanden vom Referenztyp ist, wird die Unboxing-Konvertierung (§5.1.8) durchgeführt.

§ 5.1.8 Unboxing Conversion sagt (unter anderem):

Wenn r null ist, löst die Unboxing-Konvertierung a ausNullPointerException

Joachim Sauer
quelle
0

Schreiben Sie einfach eine Methode und rufen Sie sie auf, um NullPointerException zu vermeiden.

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
Satish Hawalppagol
quelle