Почему сравнение Integer с int может вызвать исключение NullPointerException в Java? - PullRequest
73 голосов
/ 28 июля 2010

Мне было очень странно наблюдать за этой ситуацией:

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

Ответы [ 6 ]

131 голосов
/ 28 июля 2010

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

Ключевой момент таков:

  • == между двумя ссылочными типами всегда является сравнительным сравнением
    • Чаще всего, например, нет. с Integer и String вы хотите использовать equals вместо
  • == между ссылочным типом и числовым типом примитива всегда является числовым сравнением
    • Ссылочный тип будет подвергнут распаковке.
    • Распаковка null всегда выбрасывает NullPointerException
  • Хотя в Java есть много специальных обработок для 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 or double`, то выполняется тест на равенство с плавающей точкой.

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

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

Integer i = null;

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

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

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

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

Ссылки

Смежные вопросы

Смежные вопросы

13 голосов
/ 28 июля 2010

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

if ( i.intValue( ) == 0 )

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

3 голосов
/ 27 ноября 2013

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

3 голосов
/ 28 июля 2010
if (i == 0) {  //NullPointerException
   ...
}

я - целое число, а 0 - целое число, так что в действительности то, что делается, выглядит примерно так

i.intValue() == 0

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

1 голос
/ 28 июля 2010

В 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 равно нулю, преобразование при распаковке выдает NullPointerException

1 голос
/ 28 июля 2010

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

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

...