Булевы, условные операторы и автобоксинг
Почему этот бросок 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;
}
пока это не
public static void main(String[] args) throws Exception {
Boolean b = true ? null : false;
System.out.println(b); // null
}
?
Решение, кстати, заменит false
на Boolean.FALSE
, чтобы избежать null
, который был распакован на boolean
- что невозможно. Но это не вопрос. Вопрос в том, почему? Есть ли ссылки в JLS, которые подтверждают это поведение, особенно 2-го случая?
Ответы
Ответ 1
Разница в том, что явный тип метода returnsNull()
влияет на статическое типирование выражений во время компиляции:
E1: 'true ? returnsNull() : false' - boolean (auto-unboxing 2nd operand to boolean)
E2: 'true ? null : false' - Boolean (autoboxing of 3rd operand to Boolean)
См. Спецификацию языка Java, раздел 15.25 Условный оператор? :
-
Для E1 типы 2-го и 3-го операндов являются Boolean
и boolean
соответственно, поэтому этот пункт применяется:
Если один из второго и третьего операндов имеет тип boolean, а тип другого - типа Boolean, то тип условного выражения является логическим.
Так как тип выражения является boolean
, второй операнд должен быть принудительно введен в boolean
. Компилятор вставляет код авто-unboxing во второй операнд (возвращаемое значение returnsNull()
), чтобы сделать его boolean
. Это, конечно же, приводит к тому, что NPE из null
возвращается во время выполнения.
-
Для E2 типы 2-го и 3-го операндов <special null type>
(не Boolean
как в E1!) И boolean
соответственно, поэтому не применяется какое-либо конкретное предложение для печати (go read 'em!), Поэтому применяется заключительное предложение "в противном случае":
В противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 является типом, который возникает в результате применения преобразования бокса в S1, и пусть T2 является типом, который возникает в результате применения преобразования бокса в S2. Тип условного выражения является результатом применения преобразования захвата (§ 5.1.10) в lub (T1, T2) (§15.12.2.7).
- S1 ==
<special null type>
(см. П. 4.1) - S2 ==
boolean
- T1 == box (S1) ==
<special null type>
(см. Последний пункт в списке преобразований бокса в §5.1.7) - T2 == box (S2) == 'Boolean
- lub (T1, T2) ==
Boolean
Таким образом, тип условного выражения является Boolean
а третий операнд должен быть принужден к Boolean
. Компилятор вставляет код автоматического бокса для 3-го операнда (false
). Второй операнд не нуждается в автоматическом распаковке, как в E1
, поэтому нет автоматического unboxing NPE при возврате null
.
Этот вопрос нуждается в аналогичном анализе:
Java условный оператор?: Тип результата
Ответ 2
Линия:
Boolean b = true ? returnsNull() : false;
внутренне преобразован в:
Boolean b = true ? returnsNull().booleanValue() : false;
выполнить распаковку; таким образом: null.booleanValue()
даст NPE
Это одна из главных ловушек при использовании автобокса. Такое поведение действительно задокументировано в 5.1.8 JLS
Редактировать: я полагаю, что распаковка происходит из-за того, что третий оператор имеет логический тип, например (добавлено неявное приведение):
Boolean b = (Boolean) true ? true : false;
Ответ 3
Из Спецификация языка Java, раздел 15.25:
- Если один из второго и третьего операнды имеют тип boolean и тип другого имеет тип Boolean, то тип условного выражение булево.
Итак, первый пример пытается вызвать Boolean.booleanValue()
, чтобы преобразовать Boolean
в Boolean
в соответствии с первым правилом.
Во втором случае первый операнд имеет нулевой тип, когда второй не относится к эталонному типу, поэтому применяется преобразование с использованием автоматического преобразования:
- В противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 - тип, который результаты применения бокса преобразование в S1, а T2 - тип, возникающий в результате применения бокса преобразование в S2. Тип условное выражение - результат применения преобразования захвата (§ 5.1.10) в lub (T1, T2) (§15.12.2.7).
Ответ 4
Мы можем видеть эту проблему из байтового кода. В строке 3 основного байтового кода 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
бокс с логическим значением null, invokevirtual
метод java.lang.Boolean.booleanValue
, он будет бросать 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