¿Por qué comparar Integer con int puede lanzar NullPointerException en Java?

Fue muy confuso para mí observar esta situación:

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 ... } 

Entonces, como creo que la operación de boxeo se ejecuta primero (es decir, java intenta extraer el valor int de null ) y la operación de comparación tiene menor prioridad, esa es la razón por la cual se lanza la excepción.

La pregunta es: ¿por qué se implementa de esta manera en Java? ¿Por qué el boxeo tiene mayor prioridad que comparar referencias? ¿O por qué no implementaron la verificación contra null antes del boxeo?

Por el momento parece inconsistente cuando se lanza NullPointerException con primitivas envueltas y no se lanza con tipos de objetos verdaderos .

La respuesta corta

El punto clave es este:

  • == entre dos tipos de referencia es siempre comparación de referencia
    • En la mayoría de los casos, por ejemplo, con Integer y String , querrás usar equals lugar
  • == entre un tipo de referencia y un tipo primitivo numérico es siempre una comparación numérica
    • El tipo de referencia estará sujeto a la conversión de unboxing
    • Unboxing null siempre arroja NullPointerException
  • Si bien Java tiene muchos tratamientos especiales para String , de hecho NO es un tipo primitivo

Las declaraciones anteriores son válidas para cualquier código válido de Java. Con este entendimiento, no hay inconsistencia alguna en el fragmento que usted presentó.


La respuesta larga

Estas son las secciones relevantes de JLS:

JLS 15.21.3 Operadores de igualdad de referencia == y !=

Si los operandos de un operador de igualdad son de tipo de referencia o nulo , entonces la operación es igualdad de objeto.

Esto explica lo siguiente:

 Integer i = null; String str = null; if (i == null) { // Nothing happens } if (str == null) { // Nothing happens } if (str == "0") { // Nothing happens } 

Ambos operandos son tipos de referencia, y es por eso que == es una comparación de igualdad de referencia.

Esto también explica lo siguiente:

 System.out.println(new Integer(0) == new Integer(0)); // "false" System.out.println("X" == "x".toUpperCase()); // "false" 

Para == ser igualdad numérica, al menos uno de los operandos debe ser un tipo numérico :

JLS 15.21.1 Operadores de igualdad numérica == y !=

Si los operandos de un operador de igualdad son ambos de tipo numérico, o uno es de tipo numérico y el otro es convertible a tipo numérico, se realiza una promoción numérica binaria en los operandos. Si el tipo promovido de los operandos es int o long , entonces se realiza una prueba de igualdad de enteros; si el tipo promocionado es float or doble`, entonces se realiza una prueba de igualdad de coma flotante.

Tenga en cuenta que la promoción numérica binaria realiza conversión de conjunto de valores y conversión de unboxing.

Esto explica:

 Integer i = null; if (i == 0) { //NullPointerException } 

Aquí hay un extracto de Effective Java 2nd Edition, Item 49: Prefer primitivas a las primitivas en recuadro :

En resumen, use primitivas en lugar de primitivas en caja siempre que tenga la opción. Los tipos primitivos son más simples y rápidos. Si debe usar primitivos en caja, ¡tenga cuidado! El Autoboxing reduce la verbosidad, pero no el peligro, del uso de primitivas en caja. Cuando su progtwig compara dos primitivas encuadradas con el operador == , realiza una comparación de identidad, que seguramente no es lo que usted desea. Cuando su progtwig realiza cálculos mixtos que involucran primitivas en caja y sin caja, lo hace unboxing, y cuando su progtwig realiza unboxing, puede lanzar NullPointerException . Finalmente, cuando su progtwig contiene valores primitivos, puede generar creaciones de objetos costosas e innecesarias.

Hay lugares en los que no tiene más remedio que utilizar primitivas en recuadro, por ejemplo, generics, pero, en caso contrario, debería considerar seriamente si la decisión de utilizar primitivas en recuadro está justificada.

Referencias

  • JLS 4.2. Tipos y valores primitivos
    • “Los tipos numéricos son los tipos integrales y los tipos de coma flotante”.
  • Conversión de unboxing JLS 5.1.8
    • “Se dice que un tipo es convertible a un tipo numérico si es un tipo numérico, o es un tipo de referencia que se puede convertir a un tipo numérico mediante la conversión de unboxing”.
    • “La conversión de unboxing se convierte […] de tipo Integer a tipo int
    • “Si r es null , la conversión de unboxing arroja una NullPointerException
  • Guía de lenguaje Java / Autoboxing
  • JLS 15.21.1 Operadores de igualdad numérica == y !=
  • JLS 15.21.3 Operadores de igualdad de referencia == y !=
  • JLS 5.6.2 Promoción Numérica Binaria

Preguntas relacionadas

  • Cuando se comparan dos Integers en Java, ¿se produce un desempaquetado automático?
  • ¿Por qué son estos == pero no equals() ?
  • Java: ¿Cuál es la diferencia entre el autoboxing y el casting?

Preguntas relacionadas

  • ¿Cuál es la diferencia entre un int y un entero en Java / C #?
  • ¿Se garantiza que el nuevo entero (i) == i en Java? (¡SÍ! ¡La caja no está en la caja, no por el otro lado!)
  • ¿Por qué int num = Integer.getInteger("123") lanza NullPointerException ? (!!!)
  • Java noob: generics sobre objetos solamente? (sí, desafortunadamente)
  • Java String.equals versus ==

Su ejemplo de NPE es equivalente a este código, gracias al autoboxing :

if ( i.intValue( ) == 0 )

De ahí NPE si soy null .

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

i es un Entero y el 0 es un int así que en lo que realmente se hace es algo como esto

 i.intValue() == 0 

Y esto causa el nullPointer porque el i es nulo. Para String no tenemos esta operación, por eso no es la excepción.

Los creadores de Java podrían haber definido el operador == para actuar directamente sobre operandos de diferentes tipos, en cuyo caso se da el Integer I; int i; Integer I; int i; la comparación I==i; podría hacer la pregunta “¿ I una referencia a un Integer cuyo valor es i ?”, una pregunta que podría responderse sin dificultad incluso cuando sea nulo. Desafortunadamente, Java no verifica directamente si los operandos de diferentes tipos son iguales; en su lugar, verifica si el lenguaje permite que el tipo de cualquier operando se convierta al tipo del otro y, si lo hace, compara el operando convertido con el no convertido. Tal comportamiento significa que para las variables x , z con algunas combinaciones de tipos, es posible tener x==y y==z pero x!=z [por ejemplo, x = 16777216f y = 16777216 z = 16777217]. También significa que la comparación I==i se traduce como “Convertir I a un int y, si eso no arroja una excepción, compárelo con i .”

Es por la función de autoboxing de Javas . El comstackdor detecta que, en el lado derecho de la comparación, está utilizando un entero primitivo y necesita también desempaquetar el valor entero de la envoltura en un valor int primitivo.

Como eso no es posible (es nulo al NullPointerException se lanza la NullPointerException .

En i == 0 Java intentará realizar un autoboxing y hacer una comparación numérica (es decir, “¿el valor almacenado en el objeto envoltorio mencionado por i el mismo que el valor 0 ?”).

Como i es null el desempaquetado arrojará una NullPointerException .

El razonamiento es el siguiente:

La primera oración de JLS § 15.21.1 Operadores de Igualdad Numérica == y! = Dice así:

Si los operandos de un operador de igualdad son ambos de tipo numérico, o uno es de tipo numérico y el otro es convertible (§5.1.8) a tipo numérico, se realiza una promoción numérica binaria en los operandos (§5.6.2).

Claramente, i es convertible a un tipo numérico y 0 es un tipo numérico, por lo que la promoción numérica binaria se realiza en los operandos.

§ 5.6.2 La promoción binaria numérica dice (entre otras cosas):

Si alguno de los operandos es de un tipo de referencia, se realiza la conversión de unboxing (§5.1.8).

§ 5.1.8 Conversión de Unboxing dice (entre otras cosas):

Si r es nulo, la conversión de unboxing arroja una NullPointerException