Bloques de inicialización estática

265

Según tengo entendido, el "bloque de inicialización estática" se usa para establecer valores de campo estático si no se puede hacer en una línea.

Pero no entiendo por qué necesitamos un bloque especial para eso. Por ejemplo, declaramos un campo como estático (sin una asignación de valor). Y luego escriba varias líneas del código que generan y asignan un valor al campo estático declarado anteriormente.

¿Por qué necesitamos estas líneas en un bloque especial como static {...}:?

romano
fuente
66
Comentarios menores, pero sería útil si pudiera exponer claramente sus suposiciones y, por lo tanto, aclarar qué respuesta es correcta. La primera vez que leí tu pregunta, mal entendidos y pensaba que sabía la diferencia entre {...}vs static {...}. (en cuyo caso Jon Skeet definitivamente respondió su pregunta mucho mejor)
David T.
1
Esta pregunta es muy poco clara; tienes a los que responden luchando y haciendo muchas conjeturas sobre lo que quisiste decir. ¿Qué le parece escribir explícitamente el bloque de inicialización estática de ejemplo que tiene en mente y su alternativa, para que las personas tengan algo claro que responder?
Don Hatch

Respuestas:

431

El bloque no estático:

{
    // Do Something...
}

Se llama cada vez que se construye una instancia de la clase. El bloque estático solo se llama una vez , cuando se inicializa la clase, sin importar cuántos objetos de ese tipo cree.

Ejemplo:

public class Test {

    static{
        System.out.println("Static");
    }

    {
        System.out.println("Non-static block");
    }

    public static void main(String[] args) {
        Test t = new Test();
        Test t2 = new Test();
    }
}

Esto imprime:

Static
Non-static block
Non-static block
Frederik Wordenskjold
fuente
107
¿Por qué es esta la respuesta aceptada? Ni siquiera responde la pregunta.
Paul Bellora
43
Responde a la pregunta: "Esto se llama cada vez que se construye la clase. El bloque estático solo se llama una vez, sin importar cuántos objetos de ese tipo se creen".
Adam Arold el
83
Para el lector curioso, el bloque no estático es realmente copiado por el compilador de Java en cada constructor que tiene la clase ( fuente ). Por lo tanto, sigue siendo el trabajo del constructor inicializar los campos.
Martin Andersson el
2
La respuesta aceptada debería ser esta: stackoverflow.com/a/2420404/363573 . Esta respuesta presenta un ejemplo de la vida real en el que necesita bloques estáticos.
Stephan
16
¿Por qué esta respuesta es rechazada de repente? Es posible que no esté de acuerdo con que esta sea la respuesta aceptada, pero ciertamente no es de ninguna manera incorrecta o engañosa. Simplemente está tratando de ayudar a la comprensión de estas construcciones del lenguaje con un ejemplo simple.
Frederik Wordenskjold
133

Si no estuvieran en un bloque de inicialización estática, ¿dónde estarían? ¿Cómo declararía una variable que solo debía ser local para los propósitos de inicialización y distinguirla de un campo? Por ejemplo, ¿cómo te gustaría escribir:

public class Foo {
    private static final int widgets;

    static {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        widgets = first + second;
    }
}

Si firsty secondno estaban en un bloque, se verían como campos. Si estuvieran en un bloque sin staticdelante, eso contaría como un bloque de inicialización de instancia en lugar de un bloque de inicialización estático, por lo que se ejecutaría una vez por instancia construida en lugar de una vez en total.

Ahora, en este caso particular, podría usar un método estático en su lugar:

public class Foo {
    private static final int widgets = getWidgets();

    static int getWidgets() {
        int first = Widgets.getFirstCount();
        int second = Widgets.getSecondCount();
        // Imagine more complex logic here which really used first/second
        return first + second;
    }
}

... pero eso no funciona cuando hay varias variables que desea asignar dentro del mismo bloque, o ninguna (por ejemplo, si solo desea registrar algo, o tal vez inicializar una biblioteca nativa).

Jon Skeet
fuente
1
¿El bloqueo estático ocurre antes de que se asignen las variables estáticas o después? private static int widgets = 0; static{widgets = 2;}
Weishi Zeng
1
Tenía curiosidad acerca de si el bloqueo estático ocurre antes de que se asignen las variables estáticas o después. Por ejemplo, private static int widgets = 0; static{widgets = 2;}descubrí que la asignación '=' ocurre en orden, lo que significa que el '' 'puesto primero se asignará primero. El ejemplo anterior le dará a los 'widgets' un valor de 2. (PS no sabía que los comentarios solo se pueden editar en 5 minutos ...)
Weishi Zeng
@WeishiZeng: Sí, esto está documentado en docs.oracle.com/javase/specs/jls/se8/html/… - punto 9.
Jon Skeet
Pero, ¿no podría utilizar también un método estático privado que tenga exactamente el mismo código que el bloque de inicialización estática y asignar widgets al método estático privado?
Zachary Kraus
1
@ Zachary: ¿Te refieres a devolver el valor y asignar el resultado de la llamada al método? Si es así, sí, cuando está asignando exactamente una variable como resultado del bloque. Editaré mi respuesta con detalles en aproximadamente 7 horas ...
Jon Skeet
103

Aquí hay un ejemplo:

  private static final HashMap<String, String> MAP = new HashMap<String, String>();
  static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

El código en la sección o secciones "estáticas" se ejecutará en el momento de la carga de la clase, antes de que se construyan instancias de la clase (y antes de que se llame a cualquier método estático desde otro lugar). De esa manera, puede asegurarse de que los recursos de la clase estén listos para usar.

También es posible tener bloques de inicializador no estáticos. Esos actúan como extensiones del conjunto de métodos constructores definidos para la clase. Se parecen a los bloques de inicializador estático, excepto que la palabra clave "estática" se omite.

Puntiagudo
fuente
44
Para ese ejemplo en particular, a veces se abusa del patrón de doble paréntesis :)
BalusC
Se puede abusar de él, pero por otro lado, limpia algunos problemas y hace que algunos tipos de código sean un poco más "sólidos". Programo en Erlang por diversión, y te enganchas al no necesitar variables locales :-)
Pointy
1
<< El código en las secciones "estáticas" se ejecutará en el momento de la carga de la clase, antes de que se construyan las instancias de la clase (y antes de que se llame a cualquier método estático desde otro lugar). De esa manera, puede asegurarse de que los recursos de la clase estén listos para usar. >> (Que "Pointy" mencionó en la respuesta anterior) este es un punto muy importante a tener en cuenta cuando se trata de la ejecución de bloque estático.
alumno
¿Podemos hacer esto usando InitializingBean después del método afterPropertiesSet?
egemen
48

También es útil cuando en realidad no desea asignar el valor a nada, como cargar alguna clase solo una vez durante el tiempo de ejecución.

P.ej

static {
    try {
        Class.forName("com.example.jdbc.Driver");
    } catch (ClassNotFoundException e) {
        throw new ExceptionInInitializerError("Cannot load JDBC driver.", e);
    }
}

Oye, hay otro beneficio, puedes usarlo para manejar excepciones. Imagine que getStuff()aquí arroja un Exceptionque realmente pertenece en un bloque de captura:

private static Object stuff = getStuff(); // Won't compile: unhandled exception.

entonces un staticinicializador es útil aquí. Puedes manejar la excepción allí.

Otro ejemplo es hacer cosas después que no se pueden hacer durante la asignación:

private static Properties config = new Properties();

static {
    try { 
        config.load(Thread.currentThread().getClassLoader().getResourceAsStream("config.properties");
    } catch (IOException e) {
        throw new ExceptionInInitializerError("Cannot load properties file.", e);
    }
}

Para volver al ejemplo del controlador JDBC, cualquier controlador JDBC decente también utiliza el staticinicializador para registrarse en el DriverManager. También vea esto y esta respuesta.

BalusC
fuente
2
Aquí yace vudú peligroso ... los inicializadores estáticos se ejecutan en el método clinit () sintético, que se sincroniza implícitamente . Esto significa que la JVM adquirirá un bloqueo en el archivo de clase en cuestión. Esto puede llevar a un punto muerto en entornos multiproceso si dos clases intentan cargarse entre sí y cada una comienza a cargarse en un hilo diferente. Ver www-01.ibm.com/support/docview.wss?uid=swg1IV48872
Ajax
@Ajax: Consideraría esto un error en el controlador JDBC en cuestión o en el código de la aplicación responsable de cargarlo. Por lo general, en el caso de controladores JDBC decentes, siempre que lo cargue solo una vez en toda la aplicación durante el inicio de la aplicación, no hay nada en cuestión.
BalusC
Sin embargo, sin duda sería un error, pero no del todo culpa del controlador JDBC. Tal vez el controlador inocentemente tiene sus propios inicializadores estáticos, y tal vez usted inicializa inocentemente esta clase junto con algunas otras en su aplicación, y, oh no, algunas clases inesperadas se cargan cíclicamente entre sí, y ahora sus bloqueos de aplicaciones. Descubrí esto gracias al punto muerto entre java.awt.AWTEvent y sun.util.logging.PlatformLogger. Solo toqué AWTEvent para decirle que se ejecute sin cabeza, y alguna otra lib terminó cargando PlatformLogger ... que AWTEvent también carga.
Ajax
1
Ambas clases terminaron sincronizadas en diferentes subprocesos, y mi compilación se bloqueó aproximadamente 1/150 ejecuciones. Entonces, ahora soy mucho más cuidadoso en la carga de clases en bloques estáticos. En el caso que mencioné anteriormente, usando un patrón de proveedor diferido en el que podría crear una clase de proveedor interino inmediatamente (sin posibilidad de punto muerto), inicializar el campo y luego, cuando realmente se accede (en un acceso de campo no sincronizado), entonces en realidad cargo las clases que pueden causar el punto muerto.
Ajax
11

Yo diría que static blockes solo azúcar sintáctico. No hay nada que puedas hacer con el staticbloqueo y no con nada más.

Para reutilizar algunos ejemplos publicados aquí.

Este código podría reescribirse sin usar staticinitialiser.

Método # 1: con static

private static final HashMap<String, String> MAP;
static {
    MAP.put("banana", "honey");
    MAP.put("peanut butter", "jelly");
    MAP.put("rice", "beans");
  }

Método # 2: sin static

private static final HashMap<String, String> MAP = getMap();
private static HashMap<String, String> getMap()
{
    HashMap<String, String> ret = new HashMap<>();
    ret.put("banana", "honey");
    ret.put("peanut butter", "jelly");
    ret.put("rice", "beans");
    return ret;
}
usuario1508893
fuente
10

Hay algunas razones reales por las que se requiere que exista:

  1. inicializando static final miembros cuya inicialización podría generar una excepción
  2. inicializando static finalmiembros con valores calculados

Las personas tienden a usar static {}bloques como una forma conveniente de inicializar cosas de las que depende la clase también en el tiempo de ejecución, como garantizar que se cargue una clase en particular (por ejemplo, controladores JDBC). Eso se puede hacer de otras maneras; sin embargo, las dos cosas que menciono anteriormente solo se pueden hacer con una construcción como el static {}bloque.

D.Shawley
fuente
8

Puede ejecutar bits de código una vez para una clase antes de construir un objeto en los bloques estáticos.

P.ej

class A {
  static int var1 = 6;
  static int var2 = 9;
  static int var3;
  static long var4;

  static Date date1;
  static Date date2;

  static {
    date1 = new Date();

    for(int cnt = 0; cnt < var2; cnt++){
      var3 += var1;
    }

    System.out.println("End first static init: " + new Date());
  }
}
Pierre-Antoine LaFayette
fuente
7

Es un error común pensar que un bloque estático solo tiene acceso a campos estáticos. Para esto, me gustaría mostrar a continuación el código que utilizo con bastante frecuencia en proyectos de la vida real (copiado parcialmente de otra respuesta en un contexto ligeramente diferente):

public enum Language { 
  ENGLISH("eng", "en", "en_GB", "en_US"),   
  GERMAN("de", "ge"),   
  CROATIAN("hr", "cro"),   
  RUSSIAN("ru"),
  BELGIAN("be",";-)");

  static final private Map<String,Language> ALIAS_MAP = new HashMap<String,Language>(); 
  static { 
    for (Language l:Language.values()) { 
      // ignoring the case by normalizing to uppercase
      ALIAS_MAP.put(l.name().toUpperCase(),l); 
      for (String alias:l.aliases) ALIAS_MAP.put(alias.toUpperCase(),l); 
    } 
  } 

  static public boolean has(String value) { 
    // ignoring the case by normalizing to uppercase
    return ALIAS_MAP.containsKey(value.toUpper()); 
  } 

  static public Language fromString(String value) { 
    if (value == null) throw new NullPointerException("alias null"); 
    Language l = ALIAS_MAP.get(value); 
    if (l == null) throw new IllegalArgumentException("Not an alias: "+value); 
    return l; 
  } 

  private List<String> aliases; 
  private Language(String... aliases) { 
    this.aliases = Arrays.asList(aliases); 
  } 
} 

Aquí el inicializador se utiliza para mantener un índice ( ALIAS_MAP), para asignar un conjunto de alias al tipo de enumeración original. Está destinado a ser una extensión del método valueOf incorporado proporcionado por el Enummismo.

Como puede ver, el inicializador estático accede incluso al privatecampo aliases. Es importante comprender que el staticbloque ya tiene acceso a las Enuminstancias de valor (p ENGLISH. Ej .). Esto se debe al orden de inicialización y ejecución en el caso de los Enumtipos , como si los static privatecampos se hubieran inicializado con instancias antes de staticque se llamaran los bloques:

  1. Las Enumconstantes que son campos estáticos implícitos. Esto requiere que el constructor Enum y los bloques de instancia, y la inicialización de la instancia también ocurran primero.
  2. static bloque e inicialización de campos estáticos en el orden de ocurrencia.

Es staticimportante tener en cuenta esta inicialización fuera de orden (constructor antes del bloque). También sucede cuando inicializamos campos estáticos con las instancias de manera similar a un Singleton (simplificaciones hechas):

public class Foo {
  static { System.out.println("Static Block 1"); }
  public static final Foo FOO = new Foo();
  static { System.out.println("Static Block 2"); }
  public Foo() { System.out.println("Constructor"); }
  static public void main(String p[]) {
    System.out.println("In Main");
    new Foo();
  }
}

Lo que vemos es el siguiente resultado:

Static Block 1
Constructor
Static Block 2
In Main
Constructor

Claro es que la inicialización estática en realidad puede ocurrir antes del constructor, e incluso después:

Simplemente acceder a Foo en el método principal hace que se cargue la clase y se inicie la inicialización estática. Pero como parte de la inicialización estática, nuevamente llamamos a los constructores para los campos estáticos, después de lo cual reanuda la inicialización estática y completa el constructor llamado desde el método principal. Situación bastante compleja para la que espero que en la codificación normal no tengamos que lidiar.

Para obtener más información sobre esto, consulte el libro " Java eficaz ".

Yoyó
fuente
1
Tener acceso a aliasesno significa que el bloque estático pueda acceder a miembros no estáticos. aliasesse accede a través de los Languagevalores devueltos por el values()método / static / . Como mencionas, el hecho de que las variables enum ya estén disponibles en ese punto es un bit inusual: los miembros no estáticos de las clases regulares no serían accesibles en esta situación.
Ignazio
El bloque estático sigue accediendo solo a campos estáticos (en el caso de su enumeración INGLÉS, ALEMÁN, ...) que en este caso son objetos. Como los campos estáticos son objetos en sí, puede acceder al campo de instancia del objeto estático.
Swami PR
1
class Foo { static final Foo Inst1; static final Foo Inst2; static{ Inst1 = new Foo("Inst1"); Inst2 = new Foo("Inst2"); } static { System.out.println("Inst1: " + Inst1.member); System.out.println("Inst2: " + Inst2.member); } private final String member; private Foo(String member){ this.member = member; } } El código anterior no es diferente del ejemplo enum y aún permite el acceso a la variable de instancia dentro del bloque estático
Swami PR
@SwamiPR, de hecho, se compila, para mi sorpresa, y tengo que estar de acuerdo en que, en principio, el código no es diferente. Tengo que releer la especificación de Java, siento que hay algo que me perdí. Buena respuesta de vuelta, gracias.
YoYo
@SwamiPR El problema realmente es que deberíamos usar un Enum. Es la mejor manera de garantizar que estamos apuntando a instancias singulares '- vea aquí . Y a sus puntos, he realizado varias actualizaciones.
YoYo
3

Si sus variables estáticas necesitan establecerse en tiempo de ejecución, entonces un static {...} bloque es muy útil.

Por ejemplo, si necesita establecer el miembro estático en un valor que se almacena en un archivo de configuración o base de datos.

También es útil cuando desea agregar valores a un Mapmiembro estático , ya que no puede agregar estos valores en la declaración inicial del miembro.

Marcus Leon
fuente
3

Entonces tiene un campo estático (también se llama "variable de clase" porque pertenece a la clase en lugar de a una instancia de la clase; en otras palabras, está asociado con la clase en lugar de con cualquier objeto) y desea inicializarlo. Entonces, si NO desea crear una instancia de esta clase y desea manipular este campo estático, puede hacerlo de tres maneras:

1- Solo inicialízalo cuando declares la variable:

static int x = 3;

2- Tener un bloque de inicialización estático:

static int x;

static {
 x=3;
}

3- Tenga un método de clase (método estático) que acceda a la variable de clase y la inicialice: esta es la alternativa al bloque estático anterior; puedes escribir un método estático privado:

public static int x=initializeX();

private static int initializeX(){
 return 3;
}

Ahora, ¿por qué usaría un bloque de inicialización estático en lugar de métodos estáticos?

Realmente depende de lo que necesita en su programa. Pero debe saber que el bloque de inicialización estático se llama una vez y la única ventaja del método de clase es que pueden reutilizarse más adelante si necesita reinicializar la variable de clase.

Digamos que tiene una matriz compleja en su programa. Lo inicializa (usando para loop, por ejemplo) y luego los valores de esta matriz cambiarán a lo largo del programa, pero luego, en algún momento, desea reiniciarlo (volver al valor inicial). En este caso, puede llamar al método estático privado. En caso de que no necesite en su programa reiniciar los valores, simplemente puede usar el bloque estático y no necesita un método estático ya que no lo usará más adelante en el programa.

Nota: los bloques estáticos se llaman en el orden en que aparecen en el código.

Ejemplo 1:

class A{
 public static int a =f();

// this is a static method
 private static int f(){
  return 3;
 }

// this is a static block
 static {
  a=5;
 }

 public static void main(String args[]) {
// As I mentioned, you do not need to create an instance of the class to use the class variable
  System.out.print(A.a); // this will print 5
 }

}

Ejemplo 2

class A{
 static {
  a=5;
 }
 public static int a =f();

 private static int f(){
  return 3;
 }

 public static void main(String args[]) {
  System.out.print(A.a); // this will print 3
 }

}
Randa Sbeity
fuente
0

Como complemento, como dijo @Pointy

El código en la sección o secciones "estáticas" se ejecutará en el momento de la carga de la clase, antes de que se construya cualquier instancia de la clase (y antes de que se llame a cualquier método estático desde otro lugar).

Se supone que se agrega System.loadLibrary("I_am_native_library")al bloque estático.

static{
    System.loadLibrary("I_am_a_library");
}

Garantizará que no se llame a ningún método nativo antes de que la biblioteca relacionada se cargue en la memoria.

Según loadLibrary del oráculo :

Si se llama a este método más de una vez con el mismo nombre de biblioteca, se ignoran la segunda y las siguientes llamadas.

Entonces, inesperadamente, poner System.loadLibrary no se usa para evitar que la biblioteca se cargue varias veces.

Eugene
fuente
0

Primero debe comprender que sus propias clases de aplicación se instancian en java.class.Classobjetos durante el tiempo de ejecución. Esto es cuando se ejecutan sus bloques estáticos. Entonces realmente puedes hacer esto:

public class Main {

    private static int myInt;

    static {
        myInt = 1;
        System.out.println("myInt is 1");
    }

    //  needed only to run this class
    public static void main(String[] args) {
    }

}

e imprimiría "myInt is 1" a la consola. Tenga en cuenta que no he instanciado ninguna clase.

Emmanuel Osimosu
fuente
0
static int B,H;
static boolean flag = true;
static{
    Scanner scan = new Scanner(System.in);
    B = scan.nextInt();
    scan.nextLine();
    H = scan.nextInt();

    if(B < 0 || H < 0){
        flag = false;
        System.out.println("java.lang.Exception: Breadth and height must be positive");
    } 
}
Vid
fuente
-1

el bloque estático se usa para cualquier tecnología para inicializar el miembro de datos estáticos de forma dinámica, o podemos decir que para la inicialización dinámica del miembro de datos estático se está utilizando el bloque estático ... Debido a la inicialización del miembro de datos no estáticos, tenemos un constructor pero no tenemos cualquier lugar donde podamos inicializar dinámicamente miembros de datos estáticos

Eg:-class Solution{
         // static int x=10;
           static int x;
       static{
        try{
          x=System.out.println();
          }
         catch(Exception e){}
        }
       }

     class Solution1{
      public static void main(String a[]){
      System.out.println(Solution.x);
        }
        }

Ahora mi static x int se inicializará dinámicamente ... porque cuando el compilador irá a Solution.x cargará la clase de solución y la carga de bloque estático en el momento de carga de la clase ... para que podamos inicializar dinámicamente ese miembro de datos estáticos ...

}

Arun
fuente