Запустите следующий код Java:
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;
Почему существует исключение NullPointerException?
Запустите следующий код 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, головоломка 8: Dos Equis:
Вычисление смешанного типа может ввести в заблуждение. Нигде это более очевидно, чем условное выражение. [...]
Правила определения типа результата условного выражения слишком велики и сложны для воспроизведения целиком, но вот три ключевых момента.
Если второй и третий операнды имеют один и тот же тип, это тип условного выражения. Другими словами, вы можете избежать всего беспорядка, избегая смешанных вычислений.
Если один из операндов имеет тип T, где T является
byte
,short
или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 Puzzlers.
В заключение, вот что предписывает Java Puzzlers:
4,1. Вычисления смешанного типа запутывают
Рецепт: избегайте вычислений смешанного типа.
При использовании оператора
?:
с числовыми операндами используйте один и тот же числовой тип для второго и третьего операндов.
Здесь цитата из Effective Java 2nd Edition, пункт 49: Предпочитают примитивные типы для примитивов в штучной упаковке:
В общем, используйте примитивы, предпочитая вложенные в бокс примитивы, когда у вас есть выбор. Примитивные типы проще и быстрее. Если вы должны использовать примитивы в штучной упаковке, будьте осторожны! Автобоксинг уменьшает многословие, но не опасность использования примитивов в штучной упаковке. Когда ваша программа сравнивает два вставных примитива с оператором
==
, это сопоставляет идентичность, что почти наверняка не то, что вы хотите. Когда ваша программа выполняет смешанные вычисления с использованием примитивов с боксами и unboxed, она распаковывается, а когда ваша программа распаковывается, она может бросатьNullPointerException
. Наконец, когда ваши программные коды приносят примитивные значения, это может привести к дорогостоящим и ненужным созданиям объектов.
Есть места, где у вас нет выбора, кроме как использовать вставные примитивы, например. дженериков, но в противном случае вам следует серьезно подумать, оправдано ли решение использовать примитивы в штучной упаковке.
Верните один и тот же тип для обоих условий, как показано ниже, и вы получите результат.
boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1 : (Double)d2;
System.out.println(d);