Booléens, opérateurs conditionnels et autoboxing

Pourquoi cela lance-t-il NullPointerException

 public static void main(Ssortingng[] args) throws Exception { Boolean b = true ? returnsNull() : false; // NPE on this line. System.out.println(b); } public static Boolean returnsNull() { return null; } 

alors que cela ne

 public static void main(Ssortingng[] args) throws Exception { Boolean b = true ? null : false; System.out.println(b); // null } 

?

La solution est de remplacer false par Boolean.FALSE pour éviter que null soit déballé de boolean – ce qui n’est pas possible. Mais ce n’est pas la question. La question est pourquoi ? Y a-t-il des références dans JLS qui confirment ce comportement, en particulier du 2ème cas?

La différence est que le type explicite de la méthode returnsNull() affecte le typage statique des expressions au moment de la compilation:

 E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean) E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean) 

Voir Spécification du langage Java, section 15.25 Opérateur conditionnel? :

  • Pour E1, les types des 2ème et 3ème opérandes sont respectivement boolean et boolean , donc cette clause s’applique:

    Si l’un des deuxième et troisième opérandes est de type booléen et que le type de l’autre est de type booléen, le type de l’expression conditionnelle est booléen.

    Comme le type de l’expression est boolean , le deuxième opérande doit être forcé à boolean . Le compilateur insère un code d’auto-unboxing dans le second opérande (valeur de retour de returnsNull() ) pour le rendre boolean . Cela provoque bien sûr le NPE à partir de la valeur null renvoyée au moment de l’exécution.

  • Pour E2, les types des 2ème et 3ème opérandes sont respectivement (et non Boolean comme E1!) Et boolean , donc aucune clause de typage spécifique ne s’applique ( allez les lire! ) . :

    Sinon, les deuxième et troisième opérandes sont respectivement des types S1 et S2. Soit T1 le type résultant de l’application de la conversion de boxe à S1 et que T2 soit le type résultant de l’application de la conversion de boxe à S2. Le type de l’expression conditionnelle est le résultat de l’application de la conversion de capture (§5.1.10) en lub (T1, T2) (§15.12.2.7).

    • S1 == (voir §4.1 )
    • S2 == boolean
    • T1 == case (S1) == (voir dernier élément de la liste des conversions de boxe au § 5.1.7 )
    • T2 == case (S2) == `Booléen
    • lub (T1, T2) == Boolean

    Le type de l’expression conditionnelle est donc Boolean et le 3ème opérande est forcé à Boolean . Le compilateur insère un code de boxe automatique pour le 3ème opérande ( false ). Le 2ème opérande n’a pas besoin de la désencapsulation automatique comme dans E1 , donc pas de NPE auto-unboxing lorsque null est retourné.


Cette question nécessite une parsing de type similaire:

Opérateur conditionnel Java?: Type de résultat

La ligne:

  Boolean b = true ? returnsNull() : false; 

est transformé en interne en:

  Boolean b = true ? returnsNull().getBoolean() : false; 

effectuer le déballage; Ainsi: null.getBoolean() un NPE

C’est l’un des principaux pièges lors de l’utilisation de l’autoboxing. Ce comportement est en effet documenté dans 5.1.8 JLS

Edit: Je pense que le unboxing est dû au troisième opérateur étant de type booléen, comme (transtypage implicite ajouté):

  Boolean b = (Boolean) true ? true : false; 

A partir de la spécification de langage Java, section 15.25 :

  • Si l’un des deuxième et troisième opérandes est de type booléen et que le type de l’autre est de type booléen, le type de l’expression conditionnelle est booléen.

Ainsi, le premier exemple tente d’appeler Boolean.booleanValue() afin de convertir Boolean en boolean selon la première règle.

Dans le second cas, le premier opérande est du type NULL, lorsque le second n’est pas du type référence, la conversion de la boîte automatique est donc appliquée:

  • Sinon, les deuxième et troisième opérandes sont respectivement des types S1 et S2. Soit T1 le type résultant de l’application de la conversion de boxe à S1 et que T2 soit le type résultant de l’application de la conversion de boxe à S2. Le type de l’expression conditionnelle est le résultat de l’application de la conversion de capture (§5.1.10) en lub (T1, T2) (§15.12.2.7).

Nous pouvons voir ce problème à partir du code octet. À la ligne 3 du code d’octet principal, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z , le booléen de valeur de null, invokevirtual la méthode java.lang.Boolean.booleanValue , il lancera NPE bien sûr.

  public static void main(java.lang.Ssortingng[]) throws java.lang.Exception; descriptor: ([Ljava/lang/Ssortingng;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: invokestatic #2 // Method returnsNull:()Ljava/lang/Boolean; 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z 6: invokestatic #4 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean; 9: astore_1 10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream; 13: aload_1 14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V 17: return LineNumberTable: line 3: 0 line 4: 10 line 5: 17 Exceptions: throws java.lang.Exception public static java.lang.Boolean returnsNull(); descriptor: ()Ljava/lang/Boolean; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: aconst_null 1: areturn LineNumberTable: line 8: 0