Выполните следующий код Java:
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
Почему там NullPointerException?
Тип возвращаемого значения условного выражения b? d1.doubleValue: d2
равно double
. Условное выражение должно иметь единственный возвращаемый тип. Следуя правилам продвижения двоичных чисел, d2
автоматически распаковывается в double
, что вызывает исключение NullPointerException
, когда d2 == null
.
Из спецификации языка, раздел §15.25:
В противном случае, если второй и третий операнды имеют типы, которые конвертируемый (§5.1.8) в числовые типы, то есть несколько случаев: ...
В противном случае применяется двоичное числовое продвижение (§5.6.2) к типам операндов и типу условное выражение - это выдвинутый тип второго и третьего операнды. Обратите внимание, что двоичное числовое продвижение выполняет распаковку конверсии (§5.1.8) и преобразование набора значений (§5.1.13).
Потому что два выражения около :
должны возвращать один и тот же тип. Это означает, что Java пытается преобразовать выражение d2
в double
. Это означает, что байт-код вызывает doubleValue ()
на d2
-> NPE.
Обычно следует избегать вычислений смешанного типа; добавление условного / тройного ?:
только ухудшает ситуацию.
Вот цитата из Java Puzzlers , Puzzle 8: Dos Equis:
Вычисления смешанного типа могут сбивать с толку. Нигде это не проявляется более явно, чем условное выражение. [...]
Правила определения типа результата условного выражения слишком длинные и сложные, чтобы их можно было воспроизвести полностью, но вот три ключевых момента.
Если второй и третий операнды имеют один и тот же тип, это тип условного выражения. Другими словами, вы можете избежать всего беспорядка, избегая вычислений смешанного типа.
Если один из операндов имеет тип T , где T - это
байт
,короткий
илиchar
], а другой операнд - постоянное выражение типаint
, значение которого может быть представлено в типе T , тип условного выражения - T .В противном случае к типам операндов применяется двоичное числовое продвижение, и тип условного выражения является повышенным типом второго и третьего операндов.
Здесь применен пункт 3, и это привело к распаковке. Когда вы распаковываете null
, естественно возникает исключение NullPointerException
.
Вот еще один пример вычислений смешанного типа и ?:
, который может удивить:
Number n = true ? Integer.valueOf(1) : Double.valueOf(2);
System.out.println(n); // "1.0"
System.out.println(n instanceof Integer); // "false"
System.out.println(n instanceof Double); // "true"
Вычисления смешанного типа являются предметом по крайней мере 3 Java Puzzler .
В заключение, вот что предписывает Java Puzzlers :
4.1. Вычисления смешанного типа сбивают с толку
Рецепт : Избегайте вычислений смешанного типа.
При использовании оператора
?:
с числовыми операндами используйте один и тот же числовой тип для второго и третьего операндов.
Вот цитата из Эффективное 2-е издание Java, пункт 49: Предпочитайте примитивные типы примитивам в штучной упаковке :
Таким образом, используйте примитивы вместо упакованных в штучную упаковку примитивно всякий раз, когда у вас есть выбор. Примитивные типы проще и быстрее. Если вы должны использовать примитивы в штучной упаковке, будьте осторожны! Автобокс снижает многословие, но не снижает опасность использования упакованных примитивов. Когда ваша программа сравнивает два упакованных примитива с оператором
==
, она выполняет сравнение идентичности, что почти наверняка не то, что вам нужно. Когда ваша программа выполняет вычисления смешанного типа с использованием упакованных и распакованных примитивов, она выполняет распаковку, а когда ваша программа выполняет распаковку, она может выброситьNullPointerException
. Наконец, когда ваша программа упаковывает примитивные значения, это может привести к созданию дорогостоящих и ненужных объектов.
Есть места, где у вас нет другого выбора, кроме как использовать упакованные примитивы, например дженерики, но в противном случае вам следует серьезно подумать, оправдано ли решение использовать упакованные примитивы.