NullPointerException с автоупаковкой в троичном выражении

Выполните следующий код Java:

boolean b = false;
Double d1 = 0d;
Double d2 = null;
Double d = b ? d1.doubleValue() : d2;

Почему там NullPointerException?

23
задан Sotirios Delimanolis 23 June 2016 в 06:55
поделиться

3 ответа

Тип возвращаемого значения условного выражения b? d1.doubleValue: d2 равно double . Условное выражение должно иметь единственный возвращаемый тип. Следуя правилам продвижения двоичных чисел, d2 автоматически распаковывается в double , что вызывает исключение NullPointerException , когда d2 == null .

Из спецификации языка, раздел §15.25:

В противном случае, если второй и третий операнды имеют типы, которые конвертируемый (§5.1.8) в числовые типы, то есть несколько случаев: ...

В противном случае применяется двоичное числовое продвижение (§5.6.2) к типам операндов и типу условное выражение - это выдвинутый тип второго и третьего операнды. Обратите внимание, что двоичное числовое продвижение выполняет распаковку конверсии (§5.1.8) и преобразование набора значений (§5.1.13).

35
ответ дан 29 November 2019 в 01:25
поделиться

Потому что два выражения около : должны возвращать один и тот же тип. Это означает, что Java пытается преобразовать выражение d2 в double . Это означает, что байт-код вызывает doubleValue () на d2 -> NPE.

15
ответ дан 29 November 2019 в 01:25
поделиться

Обычно следует избегать вычислений смешанного типа; добавление условного / тройного ?: только ухудшает ситуацию.

Вот цитата из Java Puzzlers , Puzzle 8: Dos Equis:

Вычисления смешанного типа могут сбивать с толку. Нигде это не проявляется более явно, чем условное выражение. [...]

Правила определения типа результата условного выражения слишком длинные и сложные, чтобы их можно было воспроизвести полностью, но вот три ключевых момента.

  1. Если второй и третий операнды имеют один и тот же тип, это тип условного выражения. Другими словами, вы можете избежать всего беспорядка, избегая вычислений смешанного типа.

  2. Если один из операндов имеет тип T , где T - это байт , короткий или char ], а другой операнд - постоянное выражение типа int , значение которого может быть представлено в типе T , тип условного выражения - T .

  3. В противном случае к типам операндов применяется двоичное числовое продвижение, и тип условного выражения является повышенным типом второго и третьего операндов.

Здесь применен пункт 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 . Наконец, когда ваша программа упаковывает примитивные значения, это может привести к созданию дорогостоящих и ненужных объектов.

Есть места, где у вас нет другого выбора, кроме как использовать упакованные примитивы, например дженерики, но в противном случае вам следует серьезно подумать, оправдано ли решение использовать упакованные примитивы.

Связанные вопросы

4
ответ дан 29 November 2019 в 01:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: