web-dev-qa-db-fra.com

Pourquoi comparer Integer avec int peut lever NullPointerException en Java?

C'était très déroutant pour moi d'observer cette situation:

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

Donc, comme je pense que l'opération de boxe est exécutée en premier (c'est-à-dire Java essaie d'extraire la valeur int de null) et que l'opération de comparaison a une priorité inférieure, c'est pourquoi l'exception est levée.

La question est: pourquoi est-il implémenté de cette manière en Java? Pourquoi la boxe a une priorité plus élevée que la comparaison des références? Ou pourquoi n'ont-ils pas implémenté la vérification contre null avant de boxer?

Pour le moment, il semble incohérent lorsque NullPointerException est jeté avec des primitives encapsulées et n'est pas jeté avec les types d'objet true.

74
Roman

La réponse courte

Le point clé est le suivant:

  • == Entre deux types de référence est toujours une comparaison de référence
    • Plus souvent qu'autrement, par ex. avec Integer et String, vous voudriez utiliser equals à la place
  • == Entre un type de référence et un type primitif numérique est toujours une comparaison numérique
    • Le type de référence sera soumis à une conversion de déballage
    • Unboxing null lance toujours NullPointerException
  • Bien que Java possède de nombreux traitements spéciaux pour String, ce n'est en fait PAS un type primitif

Les instructions ci-dessus s'appliquent à tout valide Java. Avec cette compréhension, il n'y a aucune incohérence dans l'extrait de code que vous avez présenté.


La réponse longue

Voici les sections JLS pertinentes:

Opérateurs d'égalité de référence JLS 15.21.3 == Et !=

Si les opérandes d'un opérateur d'égalité sont tous deux de type référence ou de type null, alors l'opération est l'égalité d'objet.

Cela explique ce qui suit:

Integer i = null;
String str = null;

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

Les deux opérandes sont des types de référence, et c'est pourquoi le == Est une comparaison d'égalité de référence.

Cela explique également les éléments suivants:

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

Pour que == Soit une égalité numérique, au moins l'un des opérandes doit être de type numérique:

Opérateurs d'égalité numérique JLS 15.21.1 == Et !=

Si les opérandes d'un opérateur d'égalité sont les deux de type numérique, ou l'un est de type numérique et l'autre est convertible en type numérique, une promotion numérique binaire est effectuée sur les opérandes. Si le type promu des opérandes est int ou long, un test d'égalité d'entier est effectué; si le type promu est float or double`, un test d'égalité à virgule flottante est effectué.

Notez que la promotion numérique binaire effectue une conversion d'ensemble de valeurs et une conversion de déballage.

Cela explique:

Integer i = null;

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

Voici un extrait de Effective Java 2nd Edition, Item 49: Préférez les primitives aux primitives encadrées:

En résumé, utilisez les primitives de préférence aux primitives encadrées chaque fois que vous avez le choix. Les types primitifs sont plus simples et plus rapides. Si vous devez utiliser des primitives encadrées, faites attention! L'autoboxing réduit la verbosité, mais pas le danger, de l'utilisation de primitives encadrées. Lorsque votre programme compare deux primitives encadrées avec l'opérateur ==, Il effectue une comparaison d'identité, ce qui n'est certainement pas ce que vous voulez. Lorsque votre programme effectue des calculs de type mixte impliquant des primitives encadrées et non encadrées, il effectue le déballage et lorsque votre programme effectue le déballage, il peut lancer NullPointerException. Enfin, lorsque votre programme contient des valeurs primitives, cela peut entraîner des créations d'objets coûteuses et inutiles.

Il y a des endroits où vous n'avez pas d'autre choix que d'utiliser des primitives encadrées, par exemple génériques, mais sinon vous devriez sérieusement considérer si une décision d'utiliser des primitives encadrées est justifiée.

Les références

Questions connexes

Questions connexes

132
polygenelubricants

Votre exemple NPE est équivalent à ce code, grâce à autoboxing:

if ( i.intValue( ) == 0 )

D'où NPE si i est null.

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

i est un entier et le 0 est un int donc dans ce qui est vraiment fait est quelque chose comme ça

i.intValue() == 0

Et cela provoque le nullPointer parce que le i est nul. Pour String, nous n'avons pas cette opération, c'est pourquoi il n'y a pas d'exception.

Les créateurs de Java auraient pu définir le == opérateur pour agir directement sur des opérandes de différents types, auquel cas étant donné Integer I; int i; La comparaison I==i; pourrait poser la question "Est-ce que I contient une référence à un Integer dont la valeur est i?" ​​- une question à laquelle on peut répondre sans difficulté même lorsque I est null. Malheureusement, Java ne vérifie pas directement si les opérandes de différents types sont égaux; à la place, il vérifie si le langage permet de convertir le type de l'un ou l'autre opérande en type de l'autre et - si il le fait - compare l'opérande converti à l'opérande non converti. Un tel comportement signifie que pour les variables x, y et z avec certaines combinaisons de types, il est possible avoir x==y et y==z mais x!=z [par exemple. x = 16777216f y = 16777216 z = 16777217]. Cela signifie également que la comparaison I==i est traduit par "Convertir I en int et, si cela ne lève pas d'exception, le comparer à i."

3
supercat

Dans i == 0 Java essaiera de décompresser automatiquement et de faire une comparaison numérique (c'est-à-dire "est la valeur stockée dans l'objet wrapper référencé par i identique à la valeur 0? ").

Puisque i est null, le déballage lancera un NullPointerException.

Le raisonnement va comme ceci:

La première phrase de JLS § 15.21.1 Opérateurs d'égalité numérique == et! = se lit comme suit:

Si les opérandes d'un opérateur d'égalité sont tous deux de type numérique, ou l'un est de type numérique et l'autre est convertible (§5.1.8) en type numérique, une promotion numérique binaire est effectuée sur les opérandes (§5.6.2).

De toute évidence, i est convertible en un type numérique et 0 est un type numérique, donc la promotion numérique binaire est effectuée sur les opérandes.

§ 5.6.2 Promotion numérique binaire dit (entre autres):

Si l'un des opérandes est de type référence, une conversion de déballage (§5.1.8) est effectuée.

§ 5.1.8 Conversion Unboxing dit (entre autres):

Si r est nul, la conversion unboxing lance un NullPointerException

1
Joachim Sauer

C'est à cause de Javas autoboxing fonctionnalité. Le compilateur détecte que sur le côté droit de la comparaison, vous utilisez un entier primitif et devez également décompresser la valeur Integer wrapper dans une valeur int primitive.

Comme ce n'est pas possible (c'est nul comme vous l'avez souligné), le NullPointerException est jeté.

1
perdian