web-dev-qa-db-fra.com

Booléens, opérateurs conditionnels et autoboxing

Pourquoi ce lancer NullPointerException

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

public static Boolean returnsNull() {
    return null;
}

alors que ce n'est pas

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

?

La solution est en passant de remplacer false par Boolean.FALSE pour éviter que null ne soit déballé dans 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?

128
BalusC

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 Java Spécifications du langage, section 15.25 Opérateur conditionnel?:

  • Pour E1, les types des 2e et 3e 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, alors le type de l'expression conditionnelle est booléen.

    Le type de l'expression étant boolean, le deuxième opérande doit être contraint à boolean. Le compilateur insère le code de décompression automatique dans le 2ème opérande (valeur de retour de returnsNull()) pour le faire taper boolean. Cela provoque bien sûr le NPE du null retourné au moment de l'exécution.

  • Pour E2, les types des 2e et 3e opérandes sont <special null type> (Pas Boolean comme dans E1!) Et boolean respectivement, donc aucune clause de typage spécifique ne s'applique ( allez les lire! ), donc la dernière clause "sinon" s'applique:

    Sinon, les deuxième et troisième opérandes sont de types S1 et S2 respectivement. Soit T1 le type résultant de l'application de la conversion de boxe à S1, et T2 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) à lub (T1, T2) (§15.12.2.7).

    • S1 == <special null type> (Voir §4.1 )
    • S2 == boolean
    • T1 == box (S1) == <special null type> (Voir le dernier élément de la liste des conversions de boxe en §5.1.7 )
    • T2 == box (S2) == `Boolean
    • lub (T1, T2) == Boolean

    Le type de l'expression conditionnelle est donc Boolean et le troisième opérande doit être contraint à Boolean. Le compilateur insère le code de boxe automatique pour le 3ème opérande (false). Le deuxième opérande n'a pas besoin de la décompression automatique comme dans E1, Donc pas de décompression automatique NPE lorsque null est retourné.


Cette question nécessite une analyse de type similaire:

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

90
Bert F

La ligne:

    Boolean b = true ? returnsNull() : false;

est transformé en interne en:

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

pour effectuer le déballage; ainsi: null.booleanValue() donnera un NPE

Il s'agit de 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 crois que le déballage est dû au fait que le troisième opérateur est de type booléen, comme (cast implicite ajouté):

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

De Spécification du 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, alors le type de l'expression conditionnelle est booléen.

Ainsi, le premier exemple essaie 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 de type nul, lorsque le second n'est pas du type de référence, la conversion de la zone automatique est donc appliquée:

  • Sinon, les deuxième et troisième opérandes sont de types S1 et S2 respectivement. Soit T1 le type résultant de l'application de la conversion de boxe à S1, et T2 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) à lub (T1, T2) (§15.12.2.7).
16
axtavt

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

    public static void main(Java.lang.String[]) throws Java.lang.Exception;
      descriptor: ([Ljava/lang/String;)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
0
Yanhui Zhou