Boolean.valueOf () produce NullPointerException a veces

115

Tengo este codigo:

package tests;

import java.util.Hashtable;

public class Tests {

    public static void main(String[] args) {

        Hashtable<String, Boolean> modifiedItems = new Hashtable<String, Boolean>();

        System.out.println("TEST 1");
        System.out.println(modifiedItems.get("item1")); // Prints null
        System.out.println("TEST 2");
        System.out.println(modifiedItems.get("item1") == null); // Prints true
        System.out.println("TEST 3");
        System.out.println(Boolean.valueOf(null)); // Prints false
        System.out.println("TEST 4");
        System.out.println(Boolean.valueOf(modifiedItems.get("item1"))); // Produces NullPointerException
        System.out.println("FINISHED!"); // Never executed
    }
}

Mi problema es que no entiendo por qué la Prueba 3 funciona bien (imprime falsey no produce NullPointerException) mientras que la Prueba 4 arroja un NullPointerException. Como puede ver en las pruebas 1 y 2 , nully modifiedItems.get("item1")son iguales y null.

El comportamiento es el mismo en Java 7 y 8.

David E
fuente
modifiedItems.get ("item1") esto es nulo, lo sabe, pero asume que pasar esto a un valueOf no terminará en un NPE?
Stultuske
16
@Stultuske: ¡Es una pregunta válida, dado que solo dos líneas arriba pasando un literal nulla la misma función no genera un NPE! Hay una buena razón para ello, pero ciertamente es confuso a primera vista :-)
psmears
25
Estoy impresionado. Esta es la pregunta de excepción de puntero nulo más interesante que he visto en años.
candied_orange
@Jeroen, esto no es un engaño de esa pregunta . Si bien es cierto que el unboxing es común a los dos problemas, no hay comparación aquí. La clave de esta pregunta es que ocurre debido a la forma en que se resuelven las sobrecargas; y eso es muy diferente de cómo ==se aplica.
Andy Turner

Respuestas:

178

Debe observar cuidadosamente qué sobrecarga se está invocando:

  • Boolean.valueOf(null)está invocando Boolean.valueOf(String). Esto no arroja un NPEincluso si se proporciona un parámetro nulo.
  • Boolean.valueOf(modifiedItems.get("item1"))está invocando Boolean.valueOf(boolean), porque modifiedItemslos valores de 'son de tipo Boolean, lo que requiere una conversión de unboxing. Dado que modifiedItems.get("item1")es null, es el desembalaje de ese valor, no el Boolean.valueOf(...), lo que arroja el NPE.

Las reglas para determinar qué sobrecarga se invoca son bastante complicadas , pero más o menos son así:

  • En una primera pasada, se busca una coincidencia de método sin permitir el boxeo / unboxing (ni los métodos de aridad variable).

    • Porque nulles un valor aceptable para a Stringpero no boolean, Boolean.valueOf(null)se corresponde con Boolean.valueOf(String)en esta pasada;
    • Booleanno es aceptable para ninguno de los dos Boolean.valueOf(String)o Boolean.valueOf(boolean), por lo que ningún método coincide en este pase para Boolean.valueOf(modifiedItems.get("item1")).
  • En una segunda pasada, se busca una coincidencia de método, lo que permite boxear / unboxing (pero aún no métodos de aridad variable).

    • A Booleanse puede desempaquetar boolean, por lo que Boolean.valueOf(boolean)se empareja Boolean.valueOf(modifiedItems.get("item1"))en este pase; pero el compilador debe insertar una conversión de unboxing para invocarla:Boolean.valueOf(modifiedItems.get("item1").booleanValue())
  • (Hay un tercer pase que permite métodos de aridad variable, pero eso no es relevante aquí, ya que los dos primeros pases coincidieron con estos casos)

Andy Turner
fuente
3
¿Podría el código ser más claro si lo usamos Boolean.valueOf(modifiedItems.get("item1").booleanValue())en el código fuente en lugar de Boolean.valueOf(modifiedItems.get("item1"))?
CausingUnderflowsEverywhere
1
@CausingUnderflowsEverywhere no realmente, es realmente difícil ver eso .booleanValue()enterrado en la expresión. Dos observaciones: 1) el (des) boxing automático es una característica deliberada de Java para eliminar el cruft sintáctico; hacerlo usted mismo es posible, pero no idiomático; 2) esto no le ayuda en absoluto - ciertamente no evita que ocurra el problema, ni proporciona ninguna información adicional cuando ocurre la falla (el seguimiento de la pila sería idéntico, porque el código ejecutado es idéntico).
Andy Turner
@CausingUnderflowsEverywhere es mejor usar herramientas para resaltar los problemas, por ejemplo, intellij le permitiría ganar información sobre NPE potencial aquí.
Andy Turner
13

Dado que modifiedItems.getdevuelve a Boolean(que no se puede convertir en a String), la firma que se usaría es Boolean.valueOf(boolean), donde Booleanse envía a una primitiva boolean. Una vez que nullse devuelve allí, la bandeja de salida falla con un NullPointerException.

Mureinik
fuente
11

Firma del método

El método Boolean.valueOf(...)tiene dos firmas:

  1. public static Boolean valueOf(boolean b)
  2. public static Boolean valueOf(String s)

Tu modifiedItemsvalor es Boolean. No se puede enviar Booleana, Stringpor lo que se elegirá la primera firma.

Unboxing booleano

En tu declaración

Boolean.valueOf(modifiedItems.get("item1"))

que se puede leer como

Boolean.valueOf(modifiedItems.get("item1").booleanValue())   

Sin embargo, modifiedItems.get("item1")devuelve , nullpor lo que básicamente tendrás

null.booleanValue()

que obviamente conduce a un NullPointerException

Al-un
fuente
Redacción incorrecta, gracias por señalar y la respuesta se actualiza después de sus comentarios. Disculpas, no he visto tu respuesta mientras escribo y veo que la mía se parece a la tuya. ¿Debo eliminar mi respuesta para evitar confusiones para OP?
Al-un
4
No lo elimine de mi cuenta. Recuerde, este no es un juego de suma cero: las personas pueden (y lo hacen) votar múltiples respuestas.
Andy Turner
3

Como Andy ya describió muy bien el motivo de NullPointerException:

que se debe al desempaquetado booleano:

Boolean.valueOf(modifiedItems.get("item1"))

convertirse en:

Boolean.valueOf(modifiedItems.get("item1").booleanValue())

en tiempo de ejecución y luego arroja NullPointerExceptionsi modifiedItems.get("item1")es nulo.

Ahora me gustaría agregar un punto más aquí que el desempaquetado de las siguientes clases a sus respectivas primitivas también puede producir una NullPointerExceptionexcepción si sus correspondientes objetos devueltos son nulos.

  1. byte - Byte
  2. char - Personaje
  3. flotar - flotar
  4. int - Entero
  5. largo largo
  6. corto - corto
  7. doble doble

Aquí está el código:

    Hashtable<String, Boolean> modifiedItems1 = new Hashtable<String, Boolean>();
    System.out.println(Boolean.valueOf(modifiedItems1.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Byte> modifiedItems2 = new Hashtable<String, Byte>();
    System.out.println(Byte.valueOf(modifiedItems2.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Character> modifiedItems3 = new Hashtable<String, Character>();
    System.out.println(Character.valueOf(modifiedItems3.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Float> modifiedItems4 = new Hashtable<String, Float>();
    System.out.println(Float.valueOf(modifiedItems4.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Integer> modifiedItems5 = new Hashtable<String, Integer>();
    System.out.println(Integer.valueOf(modifiedItems5.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Long> modifiedItems6 = new Hashtable<String, Long>();
    System.out.println(Long.valueOf(modifiedItems6.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Short> modifiedItems7 = new Hashtable<String, Short>();
    System.out.println(Short.valueOf(modifiedItems7.get("item1")));//Exception in thread "main" java.lang.NullPointerException

    Hashtable<String, Double> modifiedItems8 = new Hashtable<String, Double>();
    System.out.println(Double.valueOf(modifiedItems8.get("item1")));//Exception in thread "main" java.lang.NullPointerException
Mohit Tyagi
fuente
1
"Converted into ... at runtime" se convierte a eso en tiempo de compilación.
Andy Turner
0

Una forma de entenderlo es cuando Boolean.valueOf(null)se invoca, a java se le dice precisamente que evalúe null.

Sin embargo, cuando Boolean.valueOf(modifiedItems.get("item1"))se invoca, se le dice a java que obtenga un valor de HashTable de tipo de objeto booleano, pero no encuentra el tipo booleano sino que encuentra un callejón sin salida (nulo) a pesar de que esperaba booleano. Se lanza la excepción NullPointerException porque los creadores de esta parte de java decidieron que esta situación es una instancia de algo en el programa que va mal y necesita la atención del programador. (Ocurrió algo no intencionado).

En este caso, es más la diferencia entre declarar deliberadamente que pretendía que el nulo estuviera allí y java encontrar una referencia faltante a un objeto (nulo) donde se pretendía encontrar un objeto.

Consulte más información sobre NullPointerException en esta respuesta: https://stackoverflow.com/a/25721181/4425643

Causando subdesbordamientos en todas partes
fuente
Si alguien puede ayudar a mejorar esta respuesta, estaba pensando en una palabra que se refiere a que el programador escribe algo con una intención clara, sin ambigüedad
CausingUnderflowsEverywhere