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


person Roman    schedule 28.07.2010    source источник
comment
Вы получите исключение NullPointerException, если сделаете str.equals (0).   -  person Ash Burlaczenko    schedule 28.07.2010
comment
Оператор == когда-то использовался для защиты от NPE при любых обстоятельствах. Для меня это просто еще один пример, демонстрирующий, насколько плохой было введение автоматического бокса в Java. Он просто не подходит по многим причинам и не предлагает ничего, чего не было раньше. Это только делает код короче, но скрывает то, что на самом деле происходит.   -  person x4u    schedule 28.07.2010
comment
Мои мысли на 180 градусов разные. Они не должны были включать примитивы, используемые объекты повсюду. Затем позвольте компилятору оптимизировать и использовать примитивы. Тогда не было бы путаницы.   -  person MrJacqes    schedule 29.07.2010


Ответы (7)


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

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

  • == between two reference types is always reference comparison
    • More often than not, e.g. with Integer and String, you'd want to use equals instead
  • == between a reference type and a numeric primitive type is always numeric comparison
    • The reference type will be subjected to unboxing conversion
    • Распаковка 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
}

Вот выдержка из Эффективное 2-е издание Java, правило 49: Предпочитайте примитивы в штучной упаковке:

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

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

использованная литература

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

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

person polygenelubricants    schedule 28.07.2010
comment
Что касается почему someRef == 0 всегда является числовым сравнением, это очень разумный выбор, поскольку сравнение ссылок двух примитивов в штучной упаковке почти всегда является ошибкой программиста. В этом случае было бы бесполезно по умолчанию ссылаться на сравнения. - person Mark Peters; 28.07.2010
comment
Почему бы компилятору не заменить выражение (myInteger == 0) на (myInteger != null && myInteger == 0) вместо того, чтобы полагаться на разработчика, который напишет этот шаблонный код проверки нуля? ИМО, я должен иметь возможность проверить if (myBoolean), и это должно оцениваться как true тогда и только тогда, когда базовое значение конкретно true - мне не нужно сначала выполнять нулевую проверку. - person Josh M.; 24.09.2018

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

if ( i.intValue( ) == 0 )

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

person Alexander Pogrebnyak    schedule 28.07.2010

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

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

i.intValue() == 0

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

person Damian Leszczyński - Vash    schedule 28.07.2010

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

person supercat    schedule 26.11.2013
comment
+1: За попытку ответить на вопрос ОП о том, почему это так устроено? - person Martijn Courteaux; 27.11.2013
comment
@MartijnCourteaux: многие языки, похоже, определяют операторы только для операндов совпадающих типов и предполагают, что если T когда-либо неявно преобразуется в U, такое неявное преобразование должно выполняться без жалоб каждый раз, когда U может быть принят, а T - нет. Если бы не такое поведение, язык мог бы определить == таким образом, что если во всех случаях, когда x==y, y==z и x==z компилируются без жалоб, три сравнения будут вести себя как отношение эквивалентности. Любопытно, что дизайнеры выдвигают всевозможные причудливые языковые функции, но игнорируют аксиоматическое соответствие. - person supercat; 27.11.2013

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

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

person Christian Seifert    schedule 28.07.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 имеет значение null, преобразование при распаковке выдает NullPointerException

person Joachim Sauer    schedule 28.07.2010

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

public static Integer getNotNullIntValue(Integer value)
{
    if(value!=null)
    {
        return value;
    }
    return 0;
}
person Satish Hawalppagol    schedule 09.08.2020