Подтвердить что ты не робот

NullPointerException с autoboxing в тройном выражении

Запустите следующий код Java:

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

Почему существует исключение NullPointerException?

4b9b3361

Ответ 1

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

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

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

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

Ответ 2

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

Ответ 3

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

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

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

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

Ответ 4

Верните один и тот же тип для обоих условий, как показано ниже, и вы получите результат.

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