Почему сравнение Целого числа с интервалом может бросить NullPointerException в Java?

Это очень сбивало с толку меня для наблюдения этой ситуации:

Integer i = null;
String str = null;

if (i == null) {   //Nothing happens
   ...                  
}
if (str == null) { //Nothing happens

}

if (i == 0) {  //NullPointerException
   ...
}
if (str == "0") { //Nothing happens
   ...
}

Так, как я думаю, упаковывая операцию, выполняется сначала (т.е. Java пытается извлечь международное значение из null) и операция сравнения имеет более низкий приоритет вот почему, исключение выдается.

Вопрос: почему это реализовано таким образом в Java? Почему упаковка имеет более высокий приоритет, затем сравнивающий ссылки? Или почему не сделал они реализовали проверку против null перед упаковкой?

В данный момент это выглядит непоследовательным когда NullPointerException брошен с перенесенными примитивами и не брошен с истинными типами объектов.

77
задан xan 25 March 2011 в 12:53
поделиться

5 ответов

Короткий ответ

Ключевой момент заключается в следующем:

  • == между двумя ссылочными типами - это всегда ссылочное сравнение.
    • Чаще всего, например, при использовании Integer и String, лучше использовать equals
  • == между ссылочным типом и числовым примитивным типом - это всегда числовое сравнение
    • Ссылочный тип будет подвергнут unboxing-преобразованию
    • Unboxing null всегда бросает NullPointerException
  • Хотя Java имеет много специальных процедур для String, на самом деле это НЕ примитивный тип

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


Длинный ответ

Вот соответствующие разделы JLS:

JLS 15.21.3 Операторы равенства ссылок == и !=

Если операнды оператора равенства имеют либо ссылочный тип, либо тип null, то операция является равенством объектов.

Это объясняет следующее:

Integer i = null;
String str = null;

if (i == null) {   // Nothing happens
}
if (str == null) { // Nothing happens
}
if (str == "0") {  // Nothing happens
}

Оба операнда являются ссылочными типами, поэтому == является сравнением ссылочного равенства.

Это также объясняет следующее:

System.out.println(new Integer(0) == new Integer(0)); // "false"
System.out.println("X" == "x".toUpperCase()); // "false"

Чтобы == было числовым равенством, хотя бы один из операндов должен быть числовым типом:

JLS 15.21.1 Операторы числового равенства == и ! =

Если операнды оператора равенства оба числового типа, или один из них числового типа, а другой обратим к числовому типу, то для операндов выполняется двоичное числовое продвижение. Если продвигаемый тип операндов - int или long, то выполняется проверка равенства целых чисел; если продвигаемый тип - float или double`, то выполняется проверка равенства с плавающей точкой.

Обратите внимание, что при двоичном числовом продвижении выполняется преобразование набора значений и преобразование без набора.

Это объясняет:

Integer i = null;

if (i == 0) {  //NullPointerException
}

Вот выдержка из Effective Java 2nd Edition, Item 49: Prefer primitives to boxed primitives:

В целом, используйте примитивы вместо boxed primitive всегда, когда у вас есть выбор. Примитивные типы проще и быстрее. Если вы должны использовать коробочные примитивы, будьте осторожны! Автобоксинг уменьшает многословность, но не опасность использования коробочных примитивов. Когда ваша программа сравнивает два ящичных примитива с помощью оператора ==, она выполняет сравнение тождества, что почти наверняка не то, что вам нужно. Когда ваша программа выполняет вычисления смешанного типа с участием коробочных и не коробочных примитивов, она выполняет дебоксинг, а когда ваша программа выполняет дебоксинг, она может выбросить NullPointerException. Наконец, когда ваша программа упаковывает примитивные значения, это может привести к дорогостоящему и ненужному созданию объектов.

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

Ссылки

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

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

135
ответ дан 24 November 2019 в 10:53
поделиться

Ваш пример NPE эквивалентен этому коду благодаря autoboxing :

if (i.intValue () == 0)

Следовательно, NPE if i равно нулю .

14
ответ дан 24 November 2019 в 10:53
поделиться

В i == 0 Java попытается выполнить автоматическую распаковку и выполнить числовое сравнение (т. Е. «- это значение, хранящееся в объекте оболочки, на который ссылается i то же, что и значение 0 ? ").

Поскольку i равно null , распаковка вызовет исключение NullPointerException .

Рассуждения выглядят следующим образом:

Первое предложение JLS § 15.21.1 Числовые операторы равенства == и! = читается следующим образом:

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

Очевидно, что i может быть преобразован в числовой тип, а 0 - это числовой тип, поэтому двоичное числовое продвижение выполняется для операндов.

§ 5.6.2 Двоичное числовое продвижение говорит (среди прочего):

Если какой-либо из операндов имеет ссылочный тип, выполняется преобразование распаковки (§5.1.8).

§ 5.1.8 Преобразование распаковки говорит (среди прочего):

Если r имеет значение null, преобразование распаковки вызывает исключение NullPointerException

1
ответ дан 24 November 2019 в 10:53
поделиться
if (i == 0) {  //NullPointerException
   ...
}

i - это целое число, а 0 - это целое число, поэтому в действительности делается что-то вроде этого

i.intValue() == 0

И это вызывает nullPointer, потому что i имеет значение null. Для String у нас нет этой операции, поэтому здесь нет исключения.

4
ответ дан 24 November 2019 в 10:53
поделиться

Это из-за особенности Javas autoboxing. Компилятор определяет, что в правой части сравнения вы используете примитивное целое число, и должен распаковать оберточное значение Integer в примитивное значение int.

Поскольку это невозможно (это null, как вы указали). выбрасывается NullPointerException.

1
ответ дан 24 November 2019 в 10:53
поделиться
Другие вопросы по тегам:

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