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?
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
etboolean
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 dereturnsNull()
) pour le faire taperboolean
. Cela provoque bien sûr le NPE dunull
retourné à l'exécution. -
pour E2, les types des 2e et 3e opérandes sont
<special null type>
(pasBoolean
comme dans E1!) etboolean
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 dansE1
, donc pas de NPE auto-unboxing quandnull
est retourné. - S1 = =
cette question nécessite une analyse de type similaire:
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;
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).
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