Pregunta

¿Qué son las excepciones de puntero nulo ( java.lang.NullPointerException ) y qué las causa?

¿Qué métodos / herramientas se pueden usar para determinar la causa de modo que evite que la excepción provoque que el programa finalice prematuramente?

¿Fue útil?

Solución

Cuando declaras una variable de referencia (es decir, un objeto) realmente estás creando un puntero a un objeto. Considere el siguiente código donde declara una variable de tipo primitivo int :

int x;
x = 10;

En este ejemplo, la variable x es un int y Java lo inicializará en 0 por usted. Cuando le asigna el valor de 10 en la segunda línea, su valor de 10 se escribe en la ubicación de memoria a la que hace referencia x .

Pero, cuando intenta declarar un tipo de referencia, sucede algo diferente. Tome el siguiente código:

Integer num;
num = new Integer(10);

La primera línea declara una variable llamada num , pero aún no contiene un valor primitivo. En cambio, contiene un puntero (porque el tipo es Integer que es un tipo de referencia). Como aún no ha dicho a qué apuntar, Java lo establece en null , lo que significa " Estoy señalando a nunca " ;.

En la segunda línea, la palabra clave new se usa para crear instancias (o crear) un objeto de tipo Integer y la variable de puntero num se asigna a ese objeto Integer .

La NullPointerException ocurre cuando declaras una variable pero no creas un objeto. Entonces, estás señalando algo que en realidad no existe.

Si intenta desreferenciar num ANTES de crear el objeto, obtendrá una NullPointerException . En los casos más triviales, el compilador detectará el problema y le informará que " num puede no haberse inicializado , " pero a veces puede escribir código que no crea directamente el objeto.

Por ejemplo, puede tener un método de la siguiente manera:

public void doSomething(SomeObject obj) {
   //do something to obj
}

En cuyo caso, no está creando el objeto obj , sino asumiendo que se creó antes de que se llamara al método doSomething () . Tenga en cuenta que es posible llamar al método de esta manera:

doSomething(null);

En cuyo caso, obj es nulo . Si el método pretende hacer algo al objeto pasado, es apropiado lanzar la NullPointerException porque es un error del programador y el programador necesitará esa información para fines de depuración.

Alternativamente, puede haber casos en los que el propósito del método no sea únicamente operar en el objeto pasado, y por lo tanto un parámetro nulo puede ser aceptable. En este caso, deberá verificar si hay un parámetro nulo y comportarse de manera diferente. También debe explicar esto en la documentación. Por ejemplo, doSomething () podría escribirse como:

/**
  * @param obj An optional foo for ____. May be null, in which case 
  *  the result will be ____.
  */
public void doSomething(SomeObject obj) {
    if(obj != null) {
       //do something
    } else {
       //do something else
    }
}

Finalmente, Cómo identificar la excepción & amp; causa el uso de Stack Trace

Otros consejos

Las

NullPointerException son excepciones que ocurren cuando intenta utilizar una referencia que apunta a ninguna ubicación en la memoria (nulo) como si estuviera haciendo referencia a un objeto. Llamar a un método en una referencia nula o intentar acceder a un campo de una referencia nula activará una NullPointerException . Estos son los más comunes, pero se enumeran otras formas en NullPointerException página javadoc.

Probablemente el código de ejemplo más rápido que se me ocurrió para ilustrar una NullPointerException sería:

public class Example {

    public static void main(String[] args) {
        Object obj = null;
        obj.hashCode();
    }

}

En la primera línea dentro de main , estoy estableciendo explícitamente la referencia Object obj igual a null . Esto significa que tengo una referencia, pero no apunta a ningún objeto. Después de eso, trato de tratar la referencia como si apuntara a un objeto llamando a un método. Esto da como resultado una NullPointerException porque no hay código para ejecutar en la ubicación a la que apunta la referencia.

(Esto es un tecnicismo, pero creo que vale la pena mencionar: una referencia que apunta a nulo no es lo mismo que un puntero C que apunta a una ubicación de memoria no válida. Un puntero nulo literalmente no apunta a ninguna parte , que es sutilmente diferente a apuntar a una ubicación que no es válida.)

¿Qué es una NullPointerException?

Un buen lugar para comenzar es el JavaDocs . Tienen esto cubierto:

  

Lanzado cuando una aplicación intenta usar nulo en un caso donde un   Se requiere objeto. Estos incluyen:

     
      
  • Llamar al método de instancia de un objeto nulo.
  •   
  • Acceso o modificación del campo de un objeto nulo.
  •   
  • Tomando la longitud de nulo como si fuera una matriz.
  •   
  • Acceder o modificar las ranuras de nulo como si fuera una matriz.
  •   
  • Lanzar nulo como si fuera un valor Lanzable.
  •   
     

Las aplicaciones deberían lanzar instancias de esta clase para indicar otro   usos ilegales del objeto nulo.

También es el caso de que si intenta usar una referencia nula con sincronizado , eso también arroje esta excepción, según JLS :

SynchronizedStatement:
    synchronized ( Expression ) Block
     
      
  • De lo contrario, si el valor de la Expresión es nulo, se arroja una NullPointerException .
  •   

¿Cómo lo soluciono?

Entonces tiene una NullPointerException . Como lo arreglas? Tomemos un ejemplo simple que arroja una NullPointerException :

public class Printer {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer();
        printer.print();
    }
}

Identifique los valores nulos

El primer paso es identificar exactamente qué valores están causando la excepción . Para esto, necesitamos hacer alguna depuración. Es importante aprender a leer un stacktrace . Esto le mostrará dónde se produjo la excepción:

Exception in thread "main" java.lang.NullPointerException
    at Printer.printString(Printer.java:13)
    at Printer.print(Printer.java:9)
    at Printer.main(Printer.java:19)

Aquí, vemos que la excepción se produce en la línea 13 (en el método printString ). Mire la línea y verifique qué valores son nulos por agregando declaraciones de registro o usando un depurador . Descubrimos que s es nulo, y llamar al método length en él arroja la excepción. Podemos ver que el programa deja de lanzar la excepción cuando s.length () se elimina del método.

Rastrear de dónde provienen estos valores

A continuación verifique de dónde proviene este valor. Al seguir las llamadas del método, vemos que s se pasa con printString (name) en el método print () , y < code> this.name es nulo.

Rastrear dónde deben establecerse estos valores

¿Dónde se establece this.name ? En el método setName (String) . Con un poco más de depuración, podemos ver que este método no se llama en absoluto. Si se llamó al método, asegúrese de verificar el orden al que se llaman estos métodos, y el método establecido no se llama después de el método de impresión.

Esto es suficiente para darnos una solución: agregue una llamada a printer.setName () antes de llamar a printer.print () .

Otras correcciones

La variable puede tener un valor predeterminado (y setName puede evitar que se establezca en nulo):

private String name = "";

El método print o printString puede verificar nulo , por ejemplo:

printString((name == null) ? "" : name);

O puede diseñar la clase para que name siempre tenga un valor no nulo :

public class Printer {
    private final String name;

    public Printer(String name) {
        this.name = Objects.requireNonNull(name);
    }

    public void print() {
        printString(name);
    }

    private void printString(String s) {
        System.out.println(s + " (" + s.length() + ")");
    }

    public static void main(String[] args) {
        Printer printer = new Printer("123");
        printer.print();
    }
}

Consulte también:

Todavía no puedo encontrar el problema

Si intentó depurar el problema y aún no tiene una solución, puede publicar una pregunta para obtener más ayuda, pero asegúrese de incluir lo que ha intentado hasta ahora. Como mínimo, incluya el stacktrace en la pregunta y marque los números de línea importantes en el código. Además, intente simplificar el código primero (consulte SSCCE ).

Pregunta: ¿Qué causa una NullPointerException (NPE)?

Como debe saber, los tipos de Java se dividen en tipos primitivos ( boolean , int , etc.) y tipos de referencia . Los tipos de referencia en Java le permiten utilizar el valor especial null , que es la forma Java de decir "no objeto".

Se lanza una NullPointerException en tiempo de ejecución cada vez que su programa intenta utilizar un null como si fuera una referencia real. Por ejemplo, si escribe esto:

public class Test {
    public static void main(String[] args) {
        String foo = null;
        int length = foo.length();   // HERE
    }
}

la declaración etiquetada " AQUÍ " intentará ejecutar el método length () en una referencia null , y esto arrojará una NullPointerException .

Hay muchas maneras en que puede usar un valor nulo que resultará en una NullPointerException . De hecho, las únicas cosas que puede hacer con un nulo sin causar un NPE son:

  • asignarlo a una variable de referencia o leerlo desde una variable de referencia,
  • asignarlo a un elemento de matriz o leerlo desde un elemento de matriz (¡siempre que la referencia de matriz en sí misma no sea nula!),
  • pasarlo como parámetro o devolverlo como resultado, o
  • pruébelo utilizando los operadores == o ! = , o instanceof .

Pregunta: ¿Cómo leo el stacktrace de NPE?

Suponga que compilo y ejecuto el programa anterior:

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.main(Test.java:4)
$

Primera observación: ¡la compilación tiene éxito! El problema en el programa NO es un error de compilación. Es un error de tiempo de ejecución . (Algunos IDEs pueden advertir que su programa siempre arrojará una excepción ... pero el compilador estándar javac no lo hace).

Segunda observación: cuando ejecuto el programa, genera dos líneas de "gobbledy-gook". ¡INCORRECTO! Eso no es tonto. Es un seguimiento de pila ... y proporciona información vital que lo ayudará a rastrear el error en su código, si se toma el tiempo de leerlo detenidamente.

Entonces, veamos lo que dice:

Exception in thread "main" java.lang.NullPointerException

La primera línea del seguimiento de la pila te dice varias cosas:

  • Te dice el nombre del hilo de Java en el que se lanzó la excepción. Para un programa simple con un hilo (como este), será "principal". Sigamos adelante ...
  • Te dice el nombre completo de la excepción que se produjo; es decir, java.lang.NullPointerException .
  • Si la excepción tiene un mensaje de error asociado, se generará después del nombre de la excepción. NullPointerException es inusual a este respecto, porque rara vez tiene un mensaje de error.

La segunda línea es la más importante para diagnosticar un NPE.

at Test.main(Test.java:4)

Esto nos dice varias cosas:

  • " en Test.main " dice que estábamos en el método main de la clase Test .
  • " Test.java: 4 " da el nombre del archivo fuente de la clase, Y nos dice que la declaración donde ocurrió esto está en la línea 4 del archivo.

Si cuenta las líneas en el archivo anterior, la línea 4 es la que etiqueté con el " AQUÍ " comentario.

Tenga en cuenta que, en un ejemplo más complicado, habrá muchas líneas en la traza de la pila NPE. Pero puede estar seguro de que la segunda línea (la primera "en la línea") le indicará dónde se arrojó el NPE 1 .

En resumen, el seguimiento de la pila nos dirá inequívocamente qué afirmación del programa ha arrojado el NPE.

1 - No es del todo cierto. Hay cosas llamadas excepciones anidadas ...

Pregunta: ¿Cómo puedo localizar la causa de la excepción NPE en mi código?

Esta es la parte difícil. La respuesta corta es aplicar inferencia lógica a la evidencia proporcionada por el seguimiento de la pila, el código fuente y la documentación API relevante.

Vamos a ilustrar primero con el ejemplo simple (arriba). Comenzamos observando la línea que el seguimiento de la pila nos ha dicho es donde ocurrió el NPE:

int length = foo.length(); // HERE

¿Cómo puede eso arrojar un NPE?

De hecho, solo hay una forma: solo puede suceder si foo tiene el valor nulo . Luego intentamos ejecutar el método length () en null y .... BANG!

Pero (te escucho decir) ¿qué pasa si el NPE se arrojó dentro de la llamada al método length () ?

Bueno, si eso sucediera, el seguimiento de la pila se vería diferente. El primer " en " la línea diría que la excepción se lanzó en alguna línea en la clase java.lang.String , y la línea 4 de Test.java sería la segunda " línea.

Entonces, ¿de dónde vino ese nulo ? En este caso, es obvio, y es obvio lo que debemos hacer para solucionarlo. (Asigne un valor no nulo a foo .)

Bien, intentemos con un ejemplo un poco más complicado. Esto requerirá alguna deducción lógica .

public class Test {

    private static String[] foo = new String[2];

    private static int test(String[] bar, int pos) {
        return bar[pos].length();
    }

    public static void main(String[] args) {
        int length = test(foo, 1);
    }
}

$ javac Test.java 
$ java Test
Exception in thread "main" java.lang.NullPointerException
    at Test.test(Test.java:6)
    at Test.main(Test.java:10)
$ 

Entonces ahora tenemos dos '' en '' líneas. El primero es para esta línea:

return args[pos].length();

y el segundo es para esta línea:

int length = test(foo, 1);

Mirando la primera línea, ¿cómo podría eso arrojar un NPE? Hay dos formas:

  • Si el valor de bar es nulo , entonces bar [pos] arrojará un NPE.
  • Si el valor de bar [pos] es nulo , al llamar a length () en él arrojará un NPE.

A continuación, tenemos que averiguar cuál de esos escenarios explica lo que realmente está sucediendo. Comenzaremos explorando el primero:

¿De dónde viene la bar ? Es un parámetro para la llamada al método test , y si observamos cómo se llamó a test , podemos ver que proviene del foo variable estática Además, podemos ver claramente que inicializamos foo a un valor no nulo. Eso es suficiente para descartar provisionalmente esta explicación. (En teoría, algo más podría cambiar foo a null ... pero eso no está sucediendo aquí.

¿Y qué hay de nuestro segundo escenario? Bueno, podemos ver que pos es 1 , lo que significa que foo [1] debe ser nulo . ¿Es eso posible?

De hecho lo es! Y ese es el problema. Cuando inicializamos así:

private static String[] foo = new String[2];

asignamos una String [] con dos elementos que se inicializan en null . Después de eso, no hemos cambiado el contenido de foo ... por lo que foo [1] seguirá siendo nulo .

Es como si estuviera intentando acceder a un objeto que es nulo . Considere el siguiente ejemplo:

TypeA objA;

En este momento, acaba de declarar este objeto pero no inicializado o instanciado . Y cada vez que intente acceder a cualquier propiedad o método, arrojará NullPointerException , lo cual tiene sentido.

Vea también este ejemplo a continuación:

String a = null;
System.out.println(a.toString()); // NullPointerException will be thrown

Se produce una excepción de puntero nulo cuando una aplicación intenta usar nulo en un caso donde se requiere un objeto. Estos incluyen:

  1. Llamar al método de instancia de un objeto nulo .
  2. Acceso o modificación del campo de un objeto nulo .
  3. Tomando la longitud de null como si fuera una matriz.
  4. Acceder o modificar las ranuras de null como si fuera una matriz.
  5. Lanzar nulo como si fuera un valor Lanzable.

Las aplicaciones deberían lanzar instancias de esta clase para indicar otros usos ilegales del objeto null .

Referencia: http://docs.oracle. com / javase / 8 / docs / api / java / lang / NullPointerException.html

Un puntero nulo es uno que apunta a ninguna parte. Cuando desreferencia un puntero p , dice "me da los datos en la ubicación almacenada en" p ". Cuando p es un puntero nulo , la ubicación almacenada en p es en ninguna parte , estás diciendo " dar Me los datos en la ubicación 'en ninguna parte' ''. Obviamente, no puede hacer esto, por lo que arroja una excepción de puntero nulo .

En general, es porque algo no se ha inicializado correctamente.

Ya hay muchas explicaciones para explicar cómo sucede y cómo solucionarlo, pero también debe seguir las mejores prácticas para evitar NullPointerException .

Ver también: Una buena lista de mejores prácticas

Añadiría, muy importante, que haga un buen uso del modificador final . Uso de " final " modificador siempre que sea aplicable en Java

Resumen :

  1. Utilice el modificador final para exigir una buena inicialización.
  2. Evite devolver valores nulos en los métodos, por ejemplo, devolver colecciones vacías cuando corresponda.
  3. Use anotaciones @NotNull y @Nullable
  4. Falla rápidamente y utiliza afirmaciones para evitar la propagación de objetos nulos a través de toda la aplicación cuando no deberían ser nulos.
  5. Use iguales con un objeto conocido primero: if("knownObject".equals(unknownObject)
  6. Prefiere valueOf () sobre toString ().
  7. Use métodos nulos seguros StringUtils StringUtils.isEmpty (null) .

Una excepción de puntero nulo es un indicador de que está utilizando un objeto sin inicializarlo.

Por ejemplo, a continuación hay una clase de estudiante que la usará en nuestro código.

public class Student {

    private int id;

    public int getId() {
        return this.id;
    }

    public setId(int newId) {
        this.id = newId;
    }
}

El siguiente código le ofrece una excepción de puntero nulo.

public class School {

    Student student;

    public School() {
        try {
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

Porque está utilizando student , pero olvidó inicializarlo como en código correcto que se muestra a continuación:

public class School {

    Student student;

    public School() {
        try {
            student = new Student();
            student.setId(12);
            student.getId();
        }
        catch(Exception e) {
            System.out.println("Null pointer exception");
        }
    }
}

En Java, todo (excepto los tipos primitivos) tiene la forma de una clase.

Si desea usar cualquier objeto, entonces tiene dos fases:

  1. Declarar
  2. Inicialización

Ejemplo:

  • Declaración: Objeto objeto;
  • Inicialización: object = new Object ();

Lo mismo para el concepto de matriz:

  • Declaración: Item item [] = new Item [5];
  • Inicialización: item [0] = new Item ();

Si no está dando la sección de inicialización, aparecerá la NullPointerException .

En Java todas las variables que declaras son en realidad " referencias " a los objetos (o primitivas) y no a los objetos mismos.

Cuando intenta ejecutar un método de objeto, la referencia le pide al objeto vivo que ejecute ese método. Pero si la referencia hace referencia a NULL (nada, cero, nulo, nada), entonces no hay forma de que el método se ejecute. Luego, el tiempo de ejecución le permite saber esto lanzando una NullPointerException.

Su referencia es " apuntando " a nulo, por lo tanto " Nulo - > Puntero " ;.

El objeto vive en el espacio de memoria de la VM y la única forma de acceder a él es utilizando las referencias this . Tome este ejemplo:

public class Some {
    private int id;
    public int getId(){
        return this.id;
    }
    public setId( int newId ) {
        this.id = newId;
    }
}

Y en otro lugar en su código:

Some reference = new Some();    // Point to a new object of type Some()
Some otherReference = null;     // Initiallly this points to NULL

reference.setId( 1 );           // Execute setId method, now private var id is 1

System.out.println( reference.getId() ); // Prints 1 to the console

otherReference = reference      // Now they both point to the only object.

reference = null;               // "reference" now point to null.

// But "otherReference" still point to the "real" object so this print 1 too...
System.out.println( otherReference.getId() );

// Guess what will happen
System.out.println( reference.getId() ); // :S Throws NullPointerException because "reference" is pointing to NULL remember...

Es importante saberlo: cuando no hay más referencias a un objeto (en el ejemplo anterior, cuando reference y otherReference apuntan a nulo), entonces el objeto es "inalcanzable". No hay forma de que podamos trabajar con él, por lo que este objeto está listo para ser recolectado, y en algún momento, la VM liberará la memoria utilizada por este objeto y asignará otro.

Otra aparición de una NullPointerException ocurre cuando uno declara una matriz de objetos, e inmediatamente trata de desreferenciar elementos dentro de ella.

String[] phrases = new String[10];
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}

Este NPE particular se puede evitar si se invierte el orden de comparación; a saber, use .equals en un objeto no nulo garantizado.

Todos los elementos dentro de una matriz se inicializan a su valor inicial común ; para cualquier tipo de matriz de objetos, eso significa que todos los elementos son null .

Debe debe inicializar los elementos de la matriz antes de acceder o desreferenciarlos.

String[] phrases = new String[] {"The bird", "A bird", "My bird", "Bird"};
String keyPhrase = "Bird";
for(String phrase : phrases) {
    System.out.println(phrase.equals(keyPhrase));
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top