Почему сравнение 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 бросается с обернутыми примитивами и не бросается с правда типы объектов.

675   6  

6 ответов:

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

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

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

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


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

здесь представлены актуальные ПСБ разделы:

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

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

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

Integer i = null;

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

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

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

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

ссылки

вопросы

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

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

if ( i.intValue( ) == 0 )

отсюда NPE if i и 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?- ...вопрос, на который можно было ответить без труда, даже когда I имеет значение 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 и 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, преобразования распаковывания бросает!--5-->

Comments

    Ничего не найдено.