84 votes

Pourquoi la comparaison d'un Integer avec un int peut provoquer une NullPointerException en Java ?

J'ai été très troublé 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 le pense, l'opération de boxe est exécutée en premier (c'est-à-dire que java essaie d'extraire la valeur int de l'opération de boxe). null ) et l'opération de comparaison est moins prioritaire, c'est pourquoi l'exception est levée.

La question est la suivante : pourquoi est-elle implémentée de cette manière en Java ? Pourquoi la mise en boîte 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 la boxe ?

Pour le moment, cela semble incohérent lorsque NullPointerException est lancée avec les primitives enveloppées et ne l'est pas avec la fonction verdadero les types d'objets.

0 votes

Vous obtiendrez une NullPointerException si vous faites str.equals("0").

0 votes

L'opérateur == a déjà été utilisé pour éviter les NPE en toutes circonstances. Pour moi, ce n'est qu'un exemple de plus qui démontre à quel point l'introduction de l'auto-boxing en Java était une mauvaise idée. Il n'est tout simplement pas adapté pour de nombreuses raisons et n'offre rien qui n'ait déjà été fait auparavant. Il ne fait que raccourcir le code tout en masquant ce qui se passe réellement.

0 votes

Mes pensées sont à 180 degrés différentes. Ils n'auraient pas dû inclure les primitives utilisées objets partout. Puis laisser le compilateur optimiser et utiliser les primitives. Il n'y aurait alors aucune confusion.

141voto

polygenelubricants Points 136838

La réponse courte

Le point essentiel est le suivant :

  • == entre deux types de référence est toujours une comparaison de référence
    • Le plus souvent, par exemple dans le cas de Integer y String vous devriez utiliser equals au lieu de
  • == entre un type de référence et un type numérique primitif est toujours une comparaison numérique.
    • Le type de référence fera l'objet d'une conversion au déballage.
    • Unboxing null jette toujours NullPointerException
  • Bien que Java dispose de nombreux traitements spéciaux pour String il n'est en fait PAS un type primitif.

Les déclarations ci-dessus sont valables pour toute valide Code Java. Dans ces conditions, il n'y a aucune incohérence dans l'extrait que vous avez présenté.


La longue réponse

Voici les sections pertinentes de JLS :

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

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

Ceci 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 la fonction == est une comparaison d'égalité de référence.

Ceci explique également ce qui suit :

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

Para == pour être une égalité numérique, au moins un des opérandes doit être de type numérique :

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

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

Notez que la promotion numérique binaire effectue la conversion des ensembles de valeurs et la conversion de l'unboxing.

Ceci explique :

Integer i = null;

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

Voici un extrait de Effective Java 2e édition, point 49 : Préférer 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'auto-boxage 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'option == 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 un unboxing, et lorsque votre programme effectue un unboxing, il peut jeter NullPointerException . Enfin, lorsque votre programme met en boîte 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 les génériques, mais autrement vous devriez sérieusement considérer si une décision d'utiliser des primitives encadrées est justifiée.

Références

Questions connexes

Questions connexes

2 votes

Quant à la por qué someRef == 0 est toujours une comparaison numérique, c'est un choix très judicieux puisque comparer les références de deux primitives encadrées est presque toujours une erreur du programmeur. Il serait inutile de recourir par défaut aux comparaisons par référence dans ce cas.

3 votes

Pourquoi le compilateur ne remplacerait-il pas l'expression (myInteger == 0) avec (myInteger != null && myInteger == 0) au lieu de compter sur le développeur pour écrire ce code passe-partout de vérification des nullités ? IMO, je devrais être capable de vérifier if (myBoolean) et qui devrait être évalué à true si et seulement si la valeur sous-jacente est spécifiquement true -- Je ne devrais pas avoir à vérifier la nullité d'abord.

15voto

Alexander Pogrebnyak Points 24964

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

if ( i.intValue( ) == 0 )

Donc NPE si i est null .

4voto

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

i est un Integer et le 0 est un int, donc dans la réalité ce qui est fait est quelque chose comme ceci

i.intValue() == 0

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

4voto

supercat Points 25534

Les créateurs de Java auraient pu définir le == pour agir directement sur des opérandes de types différents, dans ce cas, l'opérateur 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 il est possible de répondre sans difficulté même lorsque I est nulle. Malheureusement, Java ne vérifie pas directement si des opérandes de types différents sont égaux ; en revanche, il vérifie si le langage permet de convertir le type de l'un des opérandes en celui de l'autre et, si c'est le cas, compare l'opérande converti à l'opérande non converti. Un tel comportement signifie que pour les variables x , y y z avec certaines combinaisons de types, il est possible d'avoir x==y y y==z pero x!=z (par exemple, x=16777216f y=16777216 z=16777217). Cela signifie également que la comparaison I==i se traduit par "Convertissez-moi en un int et, si cela ne lève pas d'exception, comparez-le à i ."

0 votes

+1 : Pour avoir essayé de répondre à la question du PO : "Pourquoi est-ce conçu comme ça ?".

1 votes

@MartijnCourteaux : De nombreux langages semblent définir des opérateurs uniquement pour des opérandes de types correspondants, et supposent que si un T est jamais implicitement convertible en U, cette conversion implicite devrait être effectuée sans plainte chaque fois qu'un U pourrait être accepté mais un T ne le pourrait pas. S'il n'y avait pas un tel comportement, un langage pourrait définir == de telle sorte que si dans tous les cas où x==y , y==z y x==z compilent tous sans problème, les trois comparaisons se comporteront comme une relation d'équivalence. Il est curieux que les concepteurs mettent en avant toutes sortes de fonctionnalités de langage fantaisistes, mais ignorent la conformité axiomatique.

1voto

perdian Points 1325

C'est à cause de Javas autoboxing caractéristique. Le compilateur détecte que, du côté droit de la comparaison, vous utilisez un entier primitif et doit transformer la valeur Integer enveloppante en une valeur int primitive.

Puisque ce n'est pas possible (c'est nul comme vous l'avez souligné), la fonction NullPointerException est lancé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X