Почему сравнение Integer с int может вызвать исключение 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 пытается извлечь значение int из null) и операция сравнения имеет более низкий приоритет, поэтому возникает исключение.

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

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

6 ответов


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

ключевым моментом является следующее:

  • == между двумя ссылочными типами всегда есть ссылочное сравнение
    • чаще всего, например, с Integer и String, вы хотели бы использовать equals вместо
  • == между ссылочным типом и числовым примитивным типом всегда числовое сравнение
    • ссылочный тип будет подвергнут распаковке преобразование
    • анбоксинг null всегда бросает NullPointerException
  • в то время как Java имеет много специальных процедур для String, это на самом деле не примитивный тип

выше отчеты для любой действительный Java-код. При таком понимании в представленном вами фрагменте нет никакой непоследовательности.


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

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

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

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

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

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 or double', затем выполняется тест равенства с плавающей запятой.

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

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

Integer i = null;

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

вот выдержка от эффективное Java 2nd Edition, пункт 49: предпочитайте примитивы коробочным примитивам:

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

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

ссылки

вопросы

обзоры вопросы


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

if ( i.intValue( ) == 0 )

следовательно, NPE if i is null.


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

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

i.intValue() == 0

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


создатели Java могли бы определить == оператор для непосредственного воздействия на операнды разных типов, в этом случае задано Integer I; int i; сравнение I==i; может задать вопрос " делает I держите ссылку на Integer стоимость которого составляет i?- ...вопрос, на который можно ответить без труда, даже если ... --3--> равно null. К сожалению, Java напрямую не проверяет, равны ли операнды разных типов; вместо этого он проверяет, позволяет ли язык тип любого операнда, который будет преобразован в тип другого, и-если это произойдет-сравнивает преобразованный операнд с не преобразованным. Такое поведение означает, что для переменных x, y и z С некоторыми комбинациями типов можно иметь x==y и y==z но x!=z [например, x=16777216f y=16777216 z=16777217]. Это также означает, что сравнение I==i переводится как " преобразовать I в int и, если это не вызывает исключения, сравните его с i."


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

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


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

С i is 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