Boolesche Werte, bedingte Betreiber und Autoboxing
-
28-09-2019 - |
Frage
Warum diese throw 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;
}
, während dies nicht
public static void main(String[] args) throws Exception {
Boolean b = true ? null : false;
System.out.println(b); // null
}
Die Lösung ist übrigens false
von Boolean.FALSE
zu vermeiden null
seinem unboxed zu boolean
ersetzen --which nicht möglich ist. Aber das ist nicht die Frage. Die Frage ist Warum ? Gibt es Referenzen in JLS, die dieses Verhalten, vor allem des zweiten Fall bestätigt?
Lösung
Der Unterschied besteht darin, dass der explizite Typ der returnsNull()
Methode der statische Typisierung der Ausdrücke bei der Kompilierung beeinflusst:
E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)
E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)
Siehe Java Language Specification, Abschnitt 15.25 Conditional Operator? :
-
Für E1, die Typen der 2. und 3. Operanden sind
Boolean
undboolean
jeweils, so dass diese Klausel gilt:Wenn einer der zweiten und dritten Operanden vom Typ boolean ist und die Art des anderen ist vom Typ Boolean, dann der Typ des bedingten Ausdrucks ist boolean.
Da der Typ des Ausdrucks
boolean
ist, muss der zweite Operandboolean
dazu gezwungen werden. Die Compiler fügt Auto-Unboxing Code zum zweiten Operanden (Rückgabewert vonreturnsNull()
), um esboolean
zu machen eingeben. Dies ist natürlich bewirkt, dass die NPE vomnull
zur Laufzeit zurückgegeben. -
Für E2, Typen des 2. und 3. Operanden sind
<special null type>
(nichtBoolean
wie in E1!) Undboolean
jeweils, so dass keine spezifische Typisierung Klausel gilt ( go lesen ‚em ), die letzte so! "gilt sonst" Klausel:Ansonsten sind die zweiten und dritten Operanden sind von Typen S1 und S2 jeweils. Lassen T1 die Art sein, dass die Ergebnisse von der Anwendung zu S1 boxing Konvertierung und lass T2 die Art sein, dass die Ergebnisse aus Boxen Umwandlung zu S2 angelegt wird. Die Art des konditionalen Ausdrucks ist das Ergebnis des Anwendens capture Umwandlung (§5.1.10) zu lub (T1, T2) (§15.12.2.7).
- S1 ==
<special null type>
(siehe §4.1 ) - S2 ==
boolean
- T1 == Box (S1) ==
<special null type>
(siehe letzte Element in der Liste der Boxen Konvertierungen in §5.1.7 ) - T2 == Box (S2) == `Boolean
- lub (T1, T2) ==
Boolean
So der Typ des bedingten Ausdrucks
Boolean
und der dritte Operand mussBoolean
dazu gezwungen werden. Der Compiler fügt Auto-Boxen-Code für den dritten Operanden (false
). Der zweite Operand muss nicht den Auto-Unboxing wie inE1
, so dass kein Auto-Unboxing NPE wennnull
zurückgeführt wird. - S1 ==
Diese Frage braucht eine ähnliche Analyse:
Andere Tipps
Die Zeile:
Boolean b = true ? returnsNull() : false;
wird intern umgewandelt:
Boolean b = true ? returnsNull().booleanValue() : false;
das Unboxing auszuführen; also: null.booleanValue()
eine NPE ergibt
Dies ist eine der wichtigsten Gefahren beim Autoboxing verwenden. Dieses Verhalten ist in der Tat dokumentiert in 5.1.8 JLS
Edit: Ich glaube, das Unboxing mit dem dritten Operator gebührt der boolean-Typ ist, wie (implizite Umwandlung hinzugefügt):
Boolean b = (Boolean) true ? true : false;
Java Language Specification, Abschnitt 15.25 :
- Wenn eine der zweiten und dritten Operanden sind vom Typ boolean und Typ des anderen ist vom Typ Boolean, dann wird der Typ der bedingten Ausdruck ist boolean.
So, das erste Beispiel versucht Boolean.booleanValue()
zu konvertieren, um Boolean
zu boolean
gemäß der ersten Regel zu nennen.
Im zweiten Fall ist der erste Operand der Null-Typ, wenn der zweite nicht der Referenztyp ist, so wird Autoboxing Umwandlung angewendet:
- Ansonsten ist die zweite und dritte Operanden sind von Typen S1 und S2 beziehungsweise. Lassen T1 der Typ sein, Ergebnisse aus der Anwendung Boxen Umwandlung zu S1, und lassen T2 werden die geben, dass die Ergebnisse aus der Anwendung Boxen Umwandlung in S2. Die Art der Bedingungsausdruck ist das Ergebnis die Anwendung Capture-Umwandlung (§5.1.10) zu lub (T1, T2) (§15.12.2.7).
Wir können dieses Problem von Byte-Code sehen. In Zeile 3 von Bytecode Haupt, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
, der Box-Boolean von Wert null, invokevirtual
die Methode java.lang.Boolean.booleanValue
, wird es NPE natürlich werfen.
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