Почему это назначение вызывает NPE?
public class Npe {
static class Thing {
long value;
}
public static Map<Thing, Long> map;
public static void main(String[] args) {
Thing thing = new Thing();
method(null); // returns -1
method(thing); // returns 0
map = new HashMap<Thing, Long>();
method(null); // returns -1
method(thing); // NullPointerException thrown inside this method call
}
public static long method(Thing thing) {
if (thing == null) {
return -1;
}
Long v = (map == null) ? thing.value : map.get(thing); // NPE here
if (v == null) {
v = thing.value;
}
return v;
}
}
На 4-м вызове method()
я получаю a NullPointerException
, сброшенную по указанной строке внутри method()
. Если я реорганизую эту строку из
Long v = (map == null) ? thing.value : map.get(thing);
к
Long v;
if (map == null) {
v = thing.value;
} else {
v = map.get(thing);
}
Я не получаю NullPointerException
, и метод ведет себя так, как должен. Вопрос: ПОЧЕМУ?
Мне кажется, что компилятор ожидает, что результатом оператора ?
будет long
, чтобы он автоматически распаковывал (понижая с long
до long
) результат вызова map.get(thing)
(который может возвращать null
и, следовательно, бросать a NullPointerException
). ИМХО, следует ожидать, что результатом оператора ?
будет long
и автобоксинга (вместо long
to long
) thing.value
.
Еще лучше, если я реорганизую это утверждение:
Long v = (map == null) ? thing.value : map.get(thing);
к этому (явно отбрасывая long
до long
):
Long v = (map == null) ? (Long)thing.value : map.get(thing);
моя IDE (IntelliJ) говорит, что листинг является избыточным, но скомпилированный код работает так, как ожидалось, и не бросает NullPointerException
!:-D
Ответы
Ответ 1
Рассмотрим ваше условное выражение:
(map == null) ? thing.value : map.get(thing)
Результат этого выражения будет long
, так как тип thing.value
равен long
. См. JLS & sect; 15.25 - Условный оператор. Таблица в JLS 8 - отличное дополнение. Он разъясняет все возможные типы вывода для разных типов ввода. Так много было замешательство, связанное с типом условных выражений.
Теперь, когда вы вызываете этот метод как:
method(thing);
map
не null
, поэтому условие map == null
в вашем выражении оценивается как false
, а затем оценивает map.get(thing)
, чтобы получить результат.
Поскольку в map
нет записи, map.get(thing)
вернет null
. Но так как тип результата long
, операция unboxing выполняется на null
, что приводит к NPE
.
Теперь, когда вы явно бросаете thing.value
в long
, тип выражения становится long
. Таким образом, никакая распаковка не выполняется в результате map.get(thing)
, а null
назначается Long v
.
Ответ 2
Это мое понимание того, что происходит:
когда вы используете Long v = (map == null) ? thing.value : map.get(thing); // NPE here
map.get(thing)
возвращает Long
, который является null
, а затем пытается удалить его значение до Long
(потому что тип выражения длинный) - это вызывает NPE.
Однако, когда вы используете длинную форму, вы старательно избегаете операции unboxing на null Long.