Это очень сбивало с толку меня для наблюдения этой ситуации:
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
брошен с перенесенными примитивами и не брошен с истинными типами объектов.
Ключевой момент заключается в следующем:
==
между двумя ссылочными типами - это всегда ссылочное сравнение.
Integer
и String
, лучше использовать equals
==
между ссылочным типом и числовым примитивным типом - это всегда числовое сравнение
null
всегда бросает NullPointerException
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
. Наконец, когда ваша программа упаковывает примитивные значения, это может привести к дорогостоящему и ненужному созданию объектов.
Есть места, где у вас нет выбора, кроме как использовать коробочные примитивы, например, дженерики, но в остальных случаях вам следует серьезно подумать, оправдано ли решение использовать коробочные примитивы.
Integer
в тип int
"r
является null
, преобразование без упаковки выбрасывает NullPointerException
"==
и ! =
==
и !=
целых чисел
в Java происходит ли автообнуление?==
, но не equals()
?int num = Integer. getInteger("123")
бросает NullPointerException
? (!!!)String.equals
против ==
Ваш пример NPE эквивалентен этому коду благодаря autoboxing :
if (i.intValue () == 0)
Следовательно, NPE if i
равно нулю
.
В 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
if (i == 0) { //NullPointerException
...
}
i - это целое число, а 0 - это целое число, поэтому в действительности делается что-то вроде этого
i.intValue() == 0
И это вызывает nullPointer, потому что i имеет значение null. Для String у нас нет этой операции, поэтому здесь нет исключения.
Это из-за особенности Javas autoboxing. Компилятор определяет, что в правой части сравнения вы используете примитивное целое число, и должен распаковать оберточное значение Integer в примитивное значение int.
Поскольку это невозможно (это null, как вы указали). выбрасывается NullPointerException
.