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 d'ailleurs de remplacer false par Boolean.FALSE pour éviter que null ne soit pas boxé à boolean - ce qui n'est pas possible. Mais ce n'est pas la question. La question Est pourquoi ? Y a-t-il dans JLS des références qui confirment ce comportement, en particulier dans le second cas?

124
demandé sur BalusC 2010-10-07 17:28:51

4 réponses

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 Language Specification, section 15.25 Conditional Operator ? :

  • pour E1, les types des 2e et 3e opérandes sont Boolean et boolean respectivement., cette clause s'applique donc:

    Si on de la deuxième et de la troisième opérandes est de type booléen et le type de l'autre est de type Booléen, alors le type de l'expression conditionnelle est un booléen.

    puisque le type d'expression est boolean , le deuxième opérande doit être forcé à boolean . Le compilateur insère le code de déverrouillage automatique au 2e opérande (valeur de retour de returnsNull() ) pour le faire taper boolean . Cela provoque bien sûr le NPE du null retourné à 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, de sorte qu'aucune clause de Dactylographie spécifique ne s'applique ( allez les lire! ), de sorte que la dernière clause "par ailleurs" s'applique:

    dans les autres cas, les deuxième et troisième opérandes sont des types S1 et S2 respectivement. Que T1 soit le type qui résulte de l'application de la conversion de boxe à S1, et que T2 soit le type qui résulte de l'application de la conversion de boxe à S2. Le type d'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 == case(S1) == <special null type> (voir le dernier article de la liste des conversions de boxe en §5.1.7 )
    • T2 == case(S2) == `Boolean
    • lub (T1, T2) = = Boolean

    ainsi le type de l'expression conditionnelle est Boolean et le 3ème opérande doit être forcé à Boolean . Le compilateur insère le code d'auto-boxe pour le 3ème opérande ( false ). Le 2nd operand n'a pas besoin d'auto-unboxing comme dans E1 , donc pas de NPE auto-unboxing quand null est retourné.


cette question nécessite une analyse de type similaire:

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

88
répondu Bert F 2017-05-23 12:17:17

La ligne:

    Boolean b = true ? returnsNull() : false;

est transformé en:"

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

pour effectuer le désembuage; ainsi: null.getBoolean() donnera un NPE

C'est l'un des principaux écueils lors de l'utilisation de l'autoboxing. Ce comportement est en effet documenté dans 5.1.8 JLS

Edit: je crois que la décompression est due au fait que le troisième opérateur est de type booléen, comme (cast implicite ajouté):

   Boolean b = (Boolean) true ? true : false; 
22
répondu jjungnickel 2010-10-07 13:46:06

From Java Language Specification, section 15.25 :

  • Si on de la deuxième et de la troisième opérandes est de type booléen et le type de l'autre est de type Boolean, puis le type du conditionnel l'expression est booléenne.

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 nul, lorsque le second n'est pas du type de référence, de sorte que la conversion de l'autoboxing est appliquée:

  • autrement, les deuxième et troisième les opérandes sont des types S1 et S2 respectivement. Que T1 soit le type qui les résultats de l'application de la boxe la conversion en S1, et que T2 soit le le type qui résulte de l'application de la boxe conversion en S2. Le type de la conditionnel l'expression est le résultat de l'application de la conversion de capture (§5.1.10) à lub (T1, T2) (§15.12.2.7).
16
répondu axtavt 2010-10-07 16:32:18

nous pouvons voir ce problème à partir du code octet. À la ligne 3 du code byte de main, 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 , il lancera NPE bien sûr.

    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
répondu Yanhui Zhou 2016-03-24 01:31:33