2012-03-13 30 views
8

¿Llamar a un método estático en una clase en Java activa los bloques de incialización estáticos para que se ejecuten?Inicializadores estáticos y métodos estáticos En Java

Empíricamente, yo diría que no. Tengo algo como esto:

public class Country { 
    static { 
     init(); 
     List<Country> countries = DataSource.read(...); // get from a DAO 
     addCountries(countries); 
    } 

    private static Map<String, Country> allCountries = null; 

    private static void init() { 
     allCountries = new HashMap<String, Country>(); 
    } 

    private static void addCountries(List<Country> countries) { 
     for (Country country : countries) { 
      if ((country.getISO() != null) && (country.getISO().length() > 0)) { 
       allCountries.put(country.getISO(), country); 
      } 
     } 
    } 

    public static Country findByISO(String cc) { 
     return allCountries.get(cc); 
    } 
} 

En el código utilizando la clase, hacer algo como:

Country country = Country.findByISO("RO"); 

El problema es que tengo un NullPointerException porque el mapa (allCountries) no se ha inicializado. Si configuro puntos de interrupción en el bloque static, puedo ver que el mapa se llena correctamente, pero es como si el método estático no tuviera conocimiento de la ejecución del inicializador.

¿Alguien puede explicar este comportamiento?


Actualización: He añadido más detalle al código. Todavía no es 1: 1 (hay varios mapas allí y más lógica), pero he examinado explícitamente las declaraciones/referencias de allCountries y están como se enumeran arriba.

Puede ver el código de inicialización completo here.

Actualización # 2: Intenté simplificar el código tanto como sea posible y lo anoté sobre la marcha. El código real tenía la declaración de la variable estática después del inicializador. Eso provocó que restableciera la referencia, como señaló Jon en la respuesta a continuación.

Modifiqué el código en mi publicación para reflejar esto, por lo que es más claro para las personas que encuentran la pregunta. Perdón por la confusión de todos. Solo estaba tratando de hacer la vida de todos más fácil :).

Gracias por sus respuestas!

+2

¿Puedes mostrar el código con el que iniciaste el mapa? – Tom

+1

Por cierto, le falta el tipo de devolución del método findByISO() en su ejemplo. –

Respuesta

26

¿Llamar a un método estático en una clase en Java activa los bloques de inicialización estáticos para que se ejecuten?

Empíricamente, yo diría que no.

Estás equivocado.

Desde el JLS section 8.7:

Un inicializador estático declarado en una clase se ejecuta cuando se inicia la clase (§12.4.2). Junto con cualquier inicializador de campo para variables de clase (§8.3.2), los inicializadores estáticos se pueden usar para inicializar las variables de clase de la clase.

Section 12.4.1 del JLS establece:

una clase o interfaz de tipo T se inicializará inmediatamente antes de la primera aparición de uno cualquiera de los siguientes:

  • T es una clase y se crea una instancia de T

  • T es una clase y se invoca un método estático declarado por T.

  • Se asigna un campo estático declarado por T.

  • Se utiliza un campo estático declarado por T y el campo no es una variable constante (§4.12.4).

  • T es una clase de nivel superior (§7.6), y se ejecuta una declaración de afirmación (§14.10) léxicamente anidada dentro de T (§8.1.3).

Esto demuestra fácilmente:

class Foo { 
    static int x = 0; 
    static { 
     x = 10; 
    } 

    static int getX() { 
     return x; 
    } 
} 

public class Test { 
    public static void main(String[] args) throws Exception { 
     System.out.println(Foo.getX()); // Prints 10 
    } 
} 

Su problema está en alguna parte del código que usted no nos muestran. Mi conjetura es que en realidad estás declarando una variable local, así:

static { 
    Map<String, Country> allCountries = new HashMap<String, Country>(); 
    // Add entries to the map 
} 

Que cueros la variable estática, dejando a la nula variable estática.Si este es el caso, basta con cambiarlo a una asignación en lugar de una declaración:

static { 
    allCountries = new HashMap<String, Country>(); 
    // Add entries to the map 
} 

EDIT: Un punto digno de mención - a pesar de que tienes init() como la primera línea de su inicializador estático, si usted es en realidad haciendo algo más antes (luego en otros inicializadores variables) que llama a otra clase, y esa clase llama a su clase Country, luego ese código se ejecutará mientras allCountries todavía es nulo.

EDITAR: Bien, ahora podemos ver su código real, he encontrado el problema. Su de código postal tiene esta:

private static Map<String, Country> allCountries; 
static { 
    ... 
} 

Pero su verdadero código tiene esta:

static { 
    ... 
} 
private static Collection<Country> allCountries = null; 

Hay dos diferencias importantes aquí:

  • se produzca la declaración de variables después de el inicio estático bloque alizer
  • La declaración de variables incluye una asignación explícita en null

La combinación de los que está arruinando: los inicializadores de variables no son todos de ejecución antes de que el inicializador estático - inicialización se produce con el fin textual.

Así que está poblando la colección ... y luego estableciendo la referencia a nulo.

Section 12.4.2 del JLS garantiza que en el paso 9 de la inicialización:

A continuación, ejecute cualquiera de los inicializadores de clase variables y inicializadores estáticos de la clase, o los inicializadores de campo de la interfaz, con el fin textual, como si fueran un solo bloque.

código Demostración:

class Foo { 

    private static String before = "before"; 

    static { 
     before = "in init"; 
     after = "in init"; 
     leftDefault = "in init"; 
    } 

    private static String after = "after"; 
    private static String leftDefault; 

    static void dump() { 
     System.out.println("before = " + before); 
     System.out.println("after = " + after); 
     System.out.println("leftDefault = " + leftDefault); 
    } 
} 

public class Test { 
    public static void main(String[] args) throws Exception { 
     Foo.dump(); 
    } 
} 

Salida:

before = in init 
after = after 
leftDefault = in init 

Así que la solución es ya sea para deshacerse de la asignación explícita a null, o para mover las declaraciones (y, por lo tanto, inicializadores) antes del inicializador estático o (mi preferencia) ambos.

+0

Gracias por la aclaración. Revisé las referencias al mapa y están bien, estoy haciendo referencia a la variable estática, no declarando una local. He publicado más código para proporcionar información. –

+0

@AlexCiminian: Bueno, definitivamente ese no es su código real: el método 'findByISO' no tiene un tipo de devolución. Aún estoy seguro de que el problema está dentro de tu código ... aunque ya tuve otra idea. Editaré –

+0

El 'init()' es en realidad la primera línea del inicializador y no llama a ninguna otra clase que tenga referencias a País. Simplemente inicializa los varios titulares dentro de la clase 'Country'. Puedes ver el código completo en el enlace de hastebin que publiqué en mi edición (al final de la publicación). –

2

Se llamará al inicializador estático cuando se carga la clase, que normalmente es cuando se menciona por primera vez. Así que llamar a un método estático de hecho activaría el inicializador si esta es la primera vez que se hace referencia a la clase.

¿Estás seguro de que la excepción del puntero nulo es allcountries.get(), y no de un nulo Country devuelto por get()? En otras palabras, ¿está seguro de que cuyo objeto es nulo?

+0

Sí, estoy seguro de que la excepción se activa debido al mapa nulo. –

2

Teóricamente, el bloque estático debería ejecutarse cuando el cargador de clases cargue la clase.

Country country = Country.findByISO("RO"); 
^ 

En su código, se inicializa la primera vez que menciona la clase País (probablemente la línea anterior).

me corrieron esto:

public class Country { 
    private static Map<String, Country> allCountries; 
    static { 
     allCountries = new HashMap<String, Country>(); 
     allCountries.put("RO", new Country()); 
    } 

    public static Country findByISO(String cc) { 
     return allCountries.get(cc); 
    } 
} 

con esto:

public class Start 
{ 
    public static void main(String[] args){ 
     Country country = Country.findByISO("RO"); 
     System.out.println(country); 
    } 
} 

y todo funcionaba correctamente. ¿Puedes publicar el rastro de la pila del error?

Diría que el problema radica en el hecho de que el bloque estático se declara antes que el campo real.

0

¿Tiene allCountries = new HashMap(); en su bloque de inicializador estático? El bloque de inicializador estático es en realidad called al class initialization.