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

Ответ 1

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

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

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

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


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

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

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

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

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

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
}

Вот выдержка из Effective Java 2nd Edition, Item 49: Предпочитайте примитивы для примитивов в штучной упаковке:

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

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

Ссылки

Связанные вопросы

Связанные вопросы

Ответ 2

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

if ( i.intValue( ) == 0 )

Следовательно, NPE, если i - null.

Ответ 3

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

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

i.intValue() == 0

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

Ответ 4

Создатели 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 переводится как "Преобразовать я в int и, если это не генерирует исключение, сравните его с I."

Ответ 5

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

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

Ответ 6

В 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 Unboxing Conversion говорит (между прочим):

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