Странное поведение при использовании Java-тернарного оператора

Когда я пишу свой код Java следующим образом:

Map<String, Long> map = new HashMap<>()
Long number =null;
if(map == null)
    number = (long) 0;
else
    number = map.get("non-existent key");

приложение работает так, как ожидалось, но когда я это делаю:

Map<String, Long> map = new HashMap<>();
Long number= (map == null) ? (long)0 : map.get("non-existent key");

Я получаю исключение NullPointerException во второй строке. Указатель отладки перескакивает со второй строки на этот метод в классе java.lang.Thread:

 /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
     private void dispatchUncaughtException(Throwable e) {
         getUncaughtExceptionHandler().uncaughtException(this, e);
     }

Что здесь происходит? Оба эти пути кода в точности эквивалентны, не так ли?


Edit

Я использую Java 1.7 U25

Ответ 1

Они не эквивалентны.

Тип этого выражения

(map == null) ? (long)0 : map.get("non-existent key");

есть long, потому что истинный результат имеет тип long.

Причина, по которой это выражение имеет тип long, из раздела §15.25 JLS:

Если один из второго и третьего операндов имеет примитивный тип T, а тип другого - результат применения преобразования бокса (§5.1.7) в T, то тип условного выражения T.

При поиске несуществующего ключа map возвращает null. Итак, Java пытается распаковать его на long. Но это null. Так что это невозможно, и вы получите NullPointerException. Вы можете исправить это, сказав:

Long number = (map == null) ? (Long)0L : map.get("non-existent key");

и тогда все будет хорошо.

Однако здесь

if(map == null)
    number = (long) 0;
else
    number = map.get("non-existent key");

поскольку number объявляется как long, что unboxing на a long никогда не происходит.

Ответ 2

Что здесь происходит? Оба эти пути кода в точности эквивалентны, не так ли?

Они не эквивалентны; у тройного оператора есть несколько предостережений.

Аргумент if-true тернарного оператора (long) 0 имеет примитивный тип long. Следовательно, аргумент if-false будет автоматически распакован с long до long (согласно JLS §15.25):

Если один из второго и третьего операндов имеет примитивный тип T, а тип другого - результат применения преобразования бокса (§5.1.7) до T, тогда тип условного выражения равен T.

Однако этот аргумент null (так как ваша карта не содержит строку "non-existent key", то есть get() возвращает null), поэтому во время процесса распаковки происходит a NullPointerException.

Ответ 3

Я прокомментировал это выше, предлагая, чтобы он map никогда не был null, но это не помогает с тройной проблемой. Как практический вопрос, проще позволить системе выполнить работу за вас. Он мог использовать Apache Commons Collections 4 и класс DefaultedMap.

import static org.apache.commons.collections4.map.DefaultedMap.defaultedMap;

Map<String, Long> map = ...;  // Ensure not null.
Map<String, Long> dMap = defaultedMap(map, 0L); 

Google Guava не имеет ничего такого же простого, но можно обернуть map с помощью метода Maps.transformValues().