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?
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).
<special null type>
(Voir §4.1 )boolean
<special null type>
(Voir le dernier élément de la liste des conversions de boxe en §5.1.7 )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:
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;
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).
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