Dlaczego to rzuca 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;
}
podczas gdy to nie jest
public static void main(String[] args) throws Exception {
Boolean b = true ? null : false;
System.out.println(b); // null
}
?
Rozwiązaniem jest przy okazji zastąpienie false
przez, Boolean.FALSE
aby uniknąć null
rozpakowania do boolean
- co nie jest możliwe. Ale to nie jest pytanie. Pytanie brzmi: dlaczego ? Czy są jakieś odniesienia w JLS, które potwierdzają to zachowanie, szczególnie w przypadku drugiego przypadku?
Odpowiedzi:
Różnica polega na tym, że jawny typ
returnsNull()
metody wpływa na statyczne typowanie wyrażeń w czasie kompilacji:E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean) E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)
Patrz specyfikacja języka Java, sekcja 15.25 Operator warunkowy? :
Dla E1 typy drugiego i trzeciego operandu to odpowiednio
Boolean
iboolean
, więc obowiązuje ta klauzula:Ponieważ typ wyrażenia to
boolean
, drugi operand musi zostać wymuszonyboolean
. Kompilator wstawia kod automatycznego rozpakowywania do drugiego operandu (wartość zwracanareturnsNull()
), aby nadać mu typboolean
. To oczywiście powoduje, że NPE jestnull
zwracane w czasie wykonywania.W przypadku E2 typy drugiego i trzeciego operandu to
<special null type>
(inneBoolean
niż w E1!) Iboolean
odpowiednio, więc nie ma zastosowania żadna określona klauzula typowania ( przeczytaj je! ), Więc obowiązuje ostatnia klauzula „inaczej”:<special null type>
(patrz §4.1 )boolean
<special null type>
(patrz ostatnia pozycja na liście konwersji bokserskich w §5.1.7 )Boolean
Tak więc typ wyrażenia warunkowego to,
Boolean
a trzeci operand musi zostać wymuszonyBoolean
. Kompilator wstawia kod auto-boxingu dla trzeciego operandu (false
). Drugi operand nie wymaga automatycznego rozpakowywania, jak wE1
, więc nie ma automatycznego rozpakowywania NPE, gdynull
jest zwracany.To pytanie wymaga podobnej analizy typu:
Operator warunkowy Java?: Typ wyniku
źródło
lub
wlub(T1,T2)
skrót?Linia:
Boolean b = true ? returnsNull() : false;
jest wewnętrznie przekształcany w:
Boolean b = true ? returnsNull().booleanValue() : false;
do wykonania rozpakowywania; w ten sposób:
null.booleanValue()
przyniesie NPEJest to jedna z głównych pułapek podczas korzystania z autoboxingu. To zachowanie jest rzeczywiście udokumentowane w 5.1.8 JLS
Edycja: Uważam, że rozpakowywanie jest spowodowane tym, że trzeci operator jest typu boolowskiego, na przykład (dodano niejawne rzutowanie):
Boolean b = (Boolean) true ? true : false;
źródło
Ze specyfikacji języka Java, sekcja 15.25 :
Tak więc, pierwszym przykładzie próbuje wywołać
Boolean.booleanValue()
w celu przekształceniaBoolean
sięboolean
jak na pierwszej reguły.W drugim przypadku pierwszy operand jest typu null, gdy drugi nie jest typu referencyjnego, więc stosowana jest konwersja autoboxu:
źródło
null
.boolean
nie jest typem referencyjnym.Możemy zobaczyć ten problem z kodu bajtowego. W linii 3 kodu bajtowego maina,
3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
boxingu Boolean o wartości null,invokevirtual
metodajava.lang.Boolean.booleanValue
, oczywiście wyrzuci 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
źródło