Boolean.valueOf () produit parfois NullPointerException

115

J'ai ce code:

package tests;

import java.util.Hashtable;

public class Tests {

    public static void main(String[] args) {

        Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();

        System.out.println("TEST 1");
        System.out.println(modifiedItems.get("item1")); // Prints null
        System.out.println("TEST 2");
        System.out.println(modifiedItems.get("item1") == null); // Prints true
        System.out.println("TEST 3");
        System.out.println(Boolean.valueOf(null)); // Prints false
        System.out.println("TEST 4");
        System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
        System.out.println("FINISHED!"); // Never executed
    }
}

Mon problème est que je ne comprends pas pourquoi le test 3 fonctionne correctement (il imprime falseet ne produit pas NullPointerException) pendant que le test 4 lance un fichier NullPointerException. Comme vous pouvez le voir dans les tests 1 et 2 , nullet modifiedItems.get("item1")sont égaux à et null.

Le comportement est le même dans Java 7 et 8.

David E
la source
modifiedItems.get ("item1") ceci est nul, vous en êtes conscient, mais vous supposez que passer ceci à un valueOf ne finira pas dans un NPE?
Stultuske
16
@Stultuske: C'est une question valide, étant donné que juste deux lignes au-dessus de passer un littéral nullà la même fonction ne génère pas de NPE! Il y a une bonne raison à cela, mais c'est certainement déroutant à première vue :-)
psmears
25
Je suis impressionné. C'est la question d'exception de pointeur nul la plus intéressante que j'ai vue depuis des années.
candied_orange
@Jeroen ce n'est pas une dupe de cette question . S'il est vrai que le déballage est commun aux deux problèmes, il n'y a pas de comparaison en cours ici. L'essentiel à propos de cette question est qu'elle se produit en raison de la manière dont les surcharges sont résolues; et c'est tout à fait différent de la façon dont ==on l'applique.
Andy Turner

Réponses:

178

Vous devez regarder attentivement quelle surcharge est invoquée:

  • Boolean.valueOf(null)appelle Boolean.valueOf(String). Cela ne jette pas unNPE même s'il est fourni avec un paramètre nul.
  • Boolean.valueOf(modifiedItems.get("item1"))appelle Boolean.valueOf(boolean), car modifiedItemsles valeurs de sont de type Boolean, ce qui nécessite une conversion de déballage. Depuis modifiedItems.get("item1")est null, il est le unboxing de cette valeur - pas Boolean.valueOf(...)- ce qui jette le NPE.

Les règles pour déterminer quelle surcharge est invoquée sont assez épineuses , mais elles vont à peu près comme ceci:

  • Dans un premier passage, une correspondance de méthode est recherchée sans autoriser le boxing / unboxing (ni les méthodes d'arité variable).

    • Parce que nullest une valeur acceptable pour a Stringmais pas boolean, Boolean.valueOf(null)correspond à Boolean.valueOf(String)dans cette passe;
    • Booleanest pas acceptable pour les deux Boolean.valueOf(String)ou Boolean.valueOf(boolean), si aucune méthode est adaptée dans ce col pour Boolean.valueOf(modifiedItems.get("item1")).
  • Dans un second passage, une correspondance de méthode est recherchée, permettant le boxing / unboxing (mais toujours pas les méthodes d'arité variable).

    • A Booleanpeut être déballé boolean, donc Boolean.valueOf(boolean)correspond Boolean.valueOf(modifiedItems.get("item1"))à cette passe; mais une conversion de déballage doit être insérée par le compilateur pour l'invoquer:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (Il y a une troisième passe permettant les méthodes d'arité variable, mais ce n'est pas pertinent ici, car les deux premières passes correspondent à ces cas)

Andy Turner
la source
3
Le code pourrait-il être plus clair si nous l'utilisons Boolean.valueOf(modifiedItems.get("item1").booleanValue())dans le code source au lieu de Boolean.valueOf(modifiedItems.get("item1"))?
CausingUnderflowsEverywhere
1
@CausingUnderflowsEverywhere pas vraiment - il est vraiment difficile de voir cela .booleanValue()enfoui dans l'expression. Deux observations: 1) l'auto (un) boxing est une fonctionnalité délibérée de Java pour supprimer les crampes syntaxiques; le faire vous-même est possible, mais pas idiomatique; 2) cela ne vous aide pas du tout - cela n'empêche certainement pas le problème de se produire, ni ne donne aucune information supplémentaire lorsque l'échec se produit (la trace de la pile serait identique, car le code exécuté est identique).
Andy Turner
@CausingUnderflowsEverywhere, il est préférable d'utiliser des outils pour mettre en évidence les problèmes, par exemple, intellij vous rapporterait des NPE potentiels ici.
Andy Turner
13

Puisque modifiedItems.getrenvoie a Boolean(qui ne peut pas être converti en a String), la signature qui serait utilisée est Boolean.valueOf(boolean), où le Booleanest envoyé à une primitive boolean. Une fois nullretourné là-bas, l'envoi échoue avec un NullPointerException.

Mureinik
la source
11

Signature de méthode

La méthode Boolean.valueOf(...)a deux signatures:

  1. public static Boolean valueOf(boolean b)
  2. public static Boolean valueOf(String s)

Votre modifiedItemsvaleur est Boolean. Vous ne pouvez pas lancer BooleanversString cas, la première signature sera donc choisie

Unboxing booléen

Dans votre déclaration

Boolean.valueOf(modifiedItems.get("item1"))

qui peut être lu comme

Boolean.valueOf(modifiedItems.get("item1").booleanValue())   

Cependant, modifiedItems.get("item1")retourne nulldonc vous aurez essentiellement

null.booleanValue()

ce qui conduit évidemment à un NullPointerException

Al-un
la source
Formulation incorrecte, merci pour le pointage et la réponse est mise à jour suite à vos commentaires. Excusez-moi, je n'ai pas vu votre réponse en écrivant et je vois que la mienne ressemble à la vôtre. Dois-je supprimer ma réponse pour éviter toute confusion avec OP?
Al-un
4
Ne le supprimez pas sur mon compte. N'oubliez pas qu'il ne s'agit pas d'un jeu à somme nulle: les gens peuvent (et font) voter pour plusieurs réponses.
Andy Turner
3

Comme Andy l'a déjà très bien décrit la raison de NullPointerException:

ce qui est dû au déballage booléen:

Boolean.valueOf(modifiedItems.get("item1"))

se convertir en:

Boolean.valueOf(modifiedItems.get("item1").booleanValue())

au moment de l'exécution, puis il jette NullPointerExceptionsi modifiedItems.get("item1")est nul.

Maintenant, je voudrais ajouter un point de plus ici que le déballage des classes suivantes à leurs primitives respectives peut également produire une NullPointerExceptionexception si leurs objets retournés correspondants sont nuls.

  1. octet - octet
  2. char - Caractère
  3. float - Float
  4. int - Entier
  5. long - Long
  6. court - court
  7. double double

Voici le code:

    Hashtable<String, Boolean> modifiedItems1 = new Hashtable<String, Boolean>();
    System.out.println(Boolean.valueOf(modifiedItems1.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Byte> modifiedItems2 = new Hashtable<String, Byte>();
    System.out.println(Byte.valueOf(modifiedItems2.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Character> modifiedItems3 = new Hashtable<String, Character>();
    System.out.println(Character.valueOf(modifiedItems3.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Float> modifiedItems4 = new Hashtable<String, Float>();
    System.out.println(Float.valueOf(modifiedItems4.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Integer> modifiedItems5 = new Hashtable<String, Integer>();
    System.out.println(Integer.valueOf(modifiedItems5.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Long> modifiedItems6 = new Hashtable<String, Long>();
    System.out.println(Long.valueOf(modifiedItems6.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Short> modifiedItems7 = new Hashtable<String, Short>();
    System.out.println(Short.valueOf(modifiedItems7.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Double> modifiedItems8 = new Hashtable<String, Double>();
    System.out.println(Double.valueOf(modifiedItems8.get("item1")));//Exception in thread "main" java.lang.NullPointerException
Mohit Tyagi
la source
1
"Converti en ... au moment de l'exécution", il est converti en celui-ci au moment de la compilation.
Andy Turner
0

Une façon de le comprendre est que lorsque Boolean.valueOf(null)est invoqué, java est précisément dit d'évaluer null.

Cependant, quand Boolean.valueOf(modifiedItems.get("item1")) est invoqué, java est invité à obtenir une valeur du HashTable de type d'objet Boolean, mais il ne trouve pas le type Boolean à la place, il trouve une impasse (null) même s'il attendait Boolean. L'exception NullPointerException est levée parce que les créateurs de cette partie de java ont décidé que cette situation est une instance de quelque chose dans le programme qui tourne mal et qui nécessite l'attention du programmeur. (Quelque chose d'inattendu s'est produit.)

Dans ce cas, c'est plus la différence entre déclarer délibérément que vous vouliez que le null soit là, et java trouver une référence manquante à un objet (null) où un objet était destiné à être trouvé.

Pour plus d'informations sur NullPointerException, consultez cette réponse: https://stackoverflow.com/a/25721181/4425643

Causer des sous-flux partout
la source
Si quelqu'un peut aider à améliorer cette réponse, je pensais à un mot qui fait référence au programmeur qui écrit quelque chose avec une intention claire, sans ambiguïté
CausingUnderflowsEverywhere