Подтвердить что ты не робот

Почему это назначение вызывает 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

4b9b3361

Ответ 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.