Странное поведение при использовании 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()
.