Почему сравнение 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
. Наконец, когда ваша программа помещает примитивные значения, это может привести к дорогостоящим и ненужным созданиям объектов.
есть места, где у вас нет выбора, кроме как использовать коробочные примитивы, например дженерики, но в противном случае вы должны серьезно рассмотреть, если решение использовать коробочные примитивы оправданы.
ссылки
-
JLS 4.2. Примитивные типы и значения
- " The числовые типы являются интегральными типами и типами с плавающей запятой."
-
JLS 5.1.8 распаковка преобразования
- " говорят, что тип преобразовать в числовой тип если это числовой тип, или это ссылочный тип, который может быть преобразуется в числовой тип путем распаковки преобразования."
- " распаковка преобразование преобразует [...] от типа
Integer
типаint
" - "если
r
иnull
, преобразования распаковывания бросает!--9-->"
- Руководство По Языку Java / Автобоксинг
- JLS 15.21.1 операторы численного равенства
==
и!=
- JLS 15.21.3 операторы равенства ссылок
==
и!=
- JLS 5.6.2 бинарное числовое продвижение
вопросы
- при сравнении двух
Integers
в Java происходит автоматическое распаковывание? - почему эти
==
а неequals()
? - Ява: какая разница между автоупаковка и литья?
обзоры вопросы
- в чем разница между int и integer в Java и в C#?
- гарантируется ли, что новое целое число (i) == i в Java? (да!!! Коробка распакована, а не наоборот!)
-
почему
int num = Integer.getInteger("123")
броситьNullPointerException
? (!!!) - Java noob: дженерики только над объектами? (да, к сожалению)
- Java
String.equals
versus==
ваш пример 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