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

81

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 null) y la operación de comparación tiene una prioridad más baja, por eso 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 nullantes del boxeo?

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

romano
fuente
Obtendría una NullPointerException si hiciera str.equals ("0").
Ash Burlaczenko
El operador == solía ser salvo contra NPE bajo cualquier circunstancia. Para mí, este es solo otro ejemplo que demuestra la mala idea que fue introducir el boxeo automático en Java. Simplemente no encaja por muchas razones y no ofrece nada que no haya estado allí antes. Solo hace que el código sea más corto mientras oculta lo que realmente está sucediendo.
x4u
Mis pensamientos son 180 grados diferentes. No deberían haber incluido los objetos primitivos usados ​​en todas partes. Luego deje que el compilador optimice y use primitivas. Entonces no habría confusión.
MrJacqes

Respuestas:

137

La respuesta corta

El punto clave es este:

  • == entre dos tipos de referencia es siempre una comparación de referencia
    • La mayoría de las veces, por ejemplo, con Integery String, querría usar equalsen su 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 conversión de desembalaje.
    • Unboxing nullsiempre arrojaNullPointerException
  • Si bien Java tiene muchos tratamientos especiales String, de hecho NO es un tipo primitivo

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


La respuesta larga

Aquí están las secciones relevantes de JLS:

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

Si los operandos de un operador de igualdad son tanto del tipo de referencia como del tipo 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 la ==comparación de igualdad de referencia es.

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 de 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 promocionado de los operandos es into long, se realiza una prueba de igualdad de enteros; si el tipo promocionado es float or double`, se realiza una prueba de igualdad de punto flotante.

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

Esto explica:

Integer i = null;

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

Aquí hay un extracto de Effective Java 2nd Edition, Item 49: Prefiere primitivas a primitivas en caja :

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 primitivas en caja, ¡tenga cuidado! Autoboxing reduce la verbosidad, pero no el peligro, de usar primitivas en caja. Cuando su programa compara dos primitivas en caja con el ==operador, hace una comparación de identidad, que casi con seguridad no es lo que desea. Cuando su programa hace cálculos de tipo mixto que involucran primitivas en caja y sin caja, lo hace unboxing, y cuando su programa lo hace, puede lanzar NullPointerException. Finalmente, cuando su programa encuadra valores primitivos, puede resultar en creaciones de objetos costosas e innecesarias.

Hay lugares en los que no tiene más remedio que utilizar primitivas en caja, por ejemplo, genéricos, pero de lo contrario debería considerar seriamente si se justifica la decisión de utilizar primitivas en caja.

Referencias

Preguntas relacionadas

Preguntas relacionadas

poligenelubricantes
fuente
2
En cuanto al por qué someRef == 0 siempre es una comparación numérica, es una elección muy acertada, ya que comparar las referencias de dos primitivas en caja es casi siempre un error del programador. Sería inútil establecer por defecto comparaciones de referencia en este caso.
Mark Peters
2
¿Por qué el compilador no reemplazaría la expresión (myInteger == 0)con en (myInteger != null && myInteger == 0)lugar de depender del desarrollador para escribir este código de verificación de nulos estándar? En mi opinión, debería poder verificar if (myBoolean)y eso debería evaluar truesi y solo si el valor subyacente es específicamente true, no debería tener que verificar nulo primero.
Josh M.
15

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

if ( i.intValue( ) == 0 )

Por tanto, NPE si ies null.

Alexander Pogrebnyak
fuente
4
if (i == 0) {  //NullPointerException
   ...
}

i es un entero y el 0 es un int, por lo que lo que realmente se hace es algo como esto

i.intValue() == 0

Y esto causa el nullPointer porque la i es nula. Para String no tenemos esta operación, por eso no hay una excepción allí.

Damian Leszczyński - Vash
fuente
4

Los creadores de Java podrían haber definido al ==operador para que actúe directamente sobre operandos de diferentes tipos, en cuyo caso, dada Integer I; int i;la comparación, I==i;podrían plantearse la pregunta "¿Tiene Iuna referencia a un Integervalor de quién es i?", Una pregunta que podría responderse sin dificultad. incluso cuando Ies nulo. Desafortunadamente, Java no verifica directamente si los operandos de diferentes tipos son iguales; en cambio, comprueba si el lenguaje permite que el tipo de cualquiera de los operandos 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, yy zcon algunas combinaciones de tipos, es posible tener x==yyy==z perox!=z[por ejemplo, x = 16777216f y = 16777216 z = 16777217]. También significa que la comparación I==ise traduce como "Convertir I en an inty, si eso no genera una excepción, compararlo con i".

Super gato
fuente
+1: Por tratar de responder a la pregunta del OP de "¿Por qué está diseñado así?"
Martijn Courteaux
1
@MartijnCourteaux: Muchos lenguajes parecen definir operadores solo para operandos de tipos coincidentes, y asumen que si una T es convertible implícitamente en U, dicha conversión implícita debe realizarse sin quejas en cualquier momento en que se pueda aceptar una U pero no una T. Si no fuera por este tipo de comportamiento, un lenguaje podría definir ==de tal manera que si en todos los casos x==y, y==zy x==ztodo sin quejarse de compilación, las tres comparaciones se comportarán como una relación de equivalencia. Es curioso que los diseñadores impulsen todo tipo de funciones de lenguaje sofisticado, pero ignoren el cumplimiento axiomático.
supercat
1

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

Dado que eso no es posible (es nulo como se alineó), NullPointerExceptionse lanza.

perdian
fuente
1

En i == 0Java intentará hacer un desempaquetado automático y hacer una comparación numérica (es decir, "¿el valor almacenado en el objeto contenedor es referenciado por iel mismo valor 0?").

Dado que ies nullel unboxing arrojará un NullPointerException.

El razonamiento es el siguiente:

La primera oración de JLS § 15.21.1 Operadores de igualdad numérica == y! = Se lee 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, la promoción numérica binaria se realiza en los operandos (§5.6.2).

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

§ 5.6.2 Promoción numérica binaria 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 Unboxing Conversion dice (entre otras cosas):

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

Joachim Sauer
fuente
0

Simplemente escriba un método y llámelo para evitar NullPointerException.

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