¿Cuál es la diferencia entre un bloque de código de inicialización estático y no estático?

357

Mi pregunta es sobre un uso particular de la palabra clave estática. Es posible usar staticpalabras clave para cubrir un bloque de código dentro de una clase que no pertenece a ninguna función. Por ejemplo, las siguientes compilaciones de código:

public class Test {
    private static final int a;    
    static {
        a = 5;
        doSomething(a);
    }
    private static int doSomething(int x) {
        return (x+5);
    }
}

Si elimina la staticpalabra clave, se queja porque la variable aes final. Sin embargo, es posible eliminar tanto finaly staticpalabras clave y que se compile.

Es confuso para mí en ambos sentidos. ¿Cómo se supone que tengo una sección de código que no pertenece a ningún método? ¿Cómo es posible invocarlo? En general, ¿cuál es el propósito de este uso? O mejor, ¿dónde puedo encontrar documentación sobre esto?

Szere Dyeri
fuente

Respuestas:

403

El bloque de código con el modificador estático significa un inicializador de clase ; sin el modificador estático, el bloque de código es un inicializador de instancia .

Los inicializadores de clase se ejecutan en el orden en que se definen (de arriba hacia abajo, al igual que los inicializadores de variables simples) cuando se carga la clase (en realidad, cuando se resuelve, pero eso es un tecnicismo).

Los inicializadores de instancia se ejecutan en el orden definido cuando se instancia la clase, inmediatamente antes de que se ejecute el código del constructor, inmediatamente después de la invocación del superconstructor.

Si elimina staticde int a, se convierte en una variable de instancia, a la que no puede acceder desde el bloque de inicializador estático. Esto no se compilará con el error "no se puede hacer referencia a una variable no estática desde un contexto estático".

Si también elimina staticdel bloque inicializador, se convierte en un inicializador de instancia y, por int alo tanto, se inicializa en la construcción.

Lawrence Dol
fuente
El inicializador estático se invoca más tarde, cuando la clase se inicializa, después de que se ha cargado y vinculado. Eso sucede cuando crea una instancia de un objeto de una clase o accede a una variable o método estático en la clase. De hecho, si tiene una clase con un inicializador estático y un método public static void staticMethod(){}, si ejecuta TestStatic.class.getMethod("staticMethod");. El inicializador estático no se invocará. Más información aquí docs.oracle.com/javase/specs/jvms/se10/html/…
Totò
@ Totò: Sí, eso es lo que implica la resolución de la clase (al menos solían referirse a ella como link + init como "resolución" en el pasado). No me sorprende que puedas usar la reflexión para descubrir cosas sobre una clase sin que se resuelva.
Lawrence Dol
166

Uff! ¿Qué es el inicializador estático?

El inicializador estático es un static {}bloque de código dentro de la clase java, y se ejecuta solo una vez antes de que se llame al constructor o al método principal.

¡OKAY! Dime más...

  • es un bloque de código static { ... }dentro de cualquier clase de Java. y ejecutado por una máquina virtual cuando se llama a la clase.
  • No returnse admiten declaraciones.
  • No se admiten argumentos.
  • No thiso superson compatibles.

Hmm, ¿dónde puedo usarlo?

Se puede usar en cualquier lugar donde te sientas bien :) así de simple. Pero veo que la mayoría de las veces se usa cuando se realiza una conexión de base de datos, API init, Logging, etc.

¡No solo ladres! donde esta el ejemplo?

package com.example.learnjava;

import java.util.ArrayList;

public class Fruit {

    static {
        System.out.println("Inside Static Initializer.");

        // fruits array
        ArrayList<String> fruits = new ArrayList<>();
        fruits.add("Apple");
        fruits.add("Orange");
        fruits.add("Pear");

        // print fruits
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        System.out.println("End Static Initializer.\n");
    }

    public static void main(String[] args) {
        System.out.println("Inside Main Method.");
    }
}

¿¿¿Salida???

Inicializador estático interior.

manzana

naranja

Pera

Finalizar inicializador estático.

Dentro del método principal.

¡Espero que esto ayude!

Madan Sapkota
fuente
Gracias madan! ¿Se puede usar el bloque estático en lugar afterPropertiesSet()de InitializingBean?
Alexander Suraphel
3
¡Sí tu puedes! El inicializador estático se llama cuando jvm carga la clase. Entonces es la primera fase en la que se ejecuta el código. Si también tiene un constructor, el orden sería: inicializador estático, constructor, afterPropertiesSet
Martin Baumgartner
57

El staticbloque es un "inicializador estático".

Se invoca automáticamente cuando se carga la clase, y no hay otra forma de invocarla (ni siquiera a través de Reflection).

Personalmente, solo lo he usado al escribir código JNI:

class JNIGlue {
    static {
        System.loadLibrary("foo");
    }
}
Alnitak
fuente
66
No, no hay forma explícita de invocarlo, el inicializador de clase nunca está representado por una Methodinstancia, sino que solo es invocado por la máquina virtual Java.
Rafael Winterhalter
46

Esto es directamente de http://www.programcreek.com/2011/10/java-class-instance-initializers/

1. Orden de ejecución

Mira la siguiente clase, ¿sabes cuál se ejecuta primero?

public class Foo {

    //instance variable initializer
    String s = "abc";

    //constructor
    public Foo() {
        System.out.println("constructor called");
    }

    //static initializer
    static {
        System.out.println("static initializer called");
    }

    //instance initializer
    {
        System.out.println("instance initializer called");
    }

    public static void main(String[] args) {
        new Foo();
        new Foo();
    }
}

Salida:

inicializador estático llamado

inicializador de instancia llamado

constructor llamado

inicializador de instancia llamado

constructor llamado

2. ¿Cómo funciona el inicializador de instancia de Java?

El inicializador de instancia anterior contiene una instrucción println. Para entender cómo funciona, podemos tratarla como una instrucción de asignación variable, por ejemplo, b = 0. Esto puede hacer que sea más obvio de entender.

En vez de

int b = 0podrías escribir

int b;
b = 0;

Por lo tanto, los inicializadores de instancia y los inicializadores de variable de instancia son más o menos lo mismo.

3. ¿Cuándo son útiles los inicializadores de instancia?

El uso de inicializadores de instancia es raro, pero aún así puede ser una alternativa útil a los inicializadores de variable de instancia si:

  1. El código de inicializador debe manejar excepciones
  2. Realice cálculos que no se puedan expresar con un inicializador de variable de instancia.

Por supuesto, dicho código podría escribirse en constructores. Pero si una clase tuviera múltiples constructores, tendría que repetir el código en cada constructor.

Con un inicializador de instancia, puede escribir el código una vez y se ejecutará sin importar qué constructor se use para crear el objeto. (Supongo que esto es solo un concepto, y no se usa con frecuencia).

Otro caso en el que los inicializadores de instancia son útiles son las clases internas anónimas, que no pueden declarar ningún constructor. (¿Será este un buen lugar para colocar una función de registro?)

Gracias a Derhein.

También tenga en cuenta que las clases anónimas que implementan interfaces [1] no tienen constructores. Por lo tanto, se necesitan inicializadores de instancia para ejecutar cualquier tipo de expresiones en tiempo de construcción.

Alexei Fando
fuente
12

"final" garantiza que se debe inicializar una variable antes del final del código de inicializador del objeto. Del mismo modo, "static final" garantiza que una variable se inicializará al final del código de inicialización de clase. Omitir el "estático" de su código de inicialización lo convierte en código de inicialización de objeto; así su variable ya no cumple sus garantías.

DJClayworth
fuente
8

No escribirá código en un bloque estático que deba invocarse en ningún lugar de su programa. Si se va a invocar el propósito del código, debe colocarlo en un método.

Puede escribir bloques de inicializador estático para inicializar variables estáticas cuando se carga la clase, pero este código puede ser más complejo.

Un bloque inicializador estático parece un método sin nombre, sin argumentos y sin tipo de retorno. Como nunca lo llamas, no necesita un nombre. El único momento en que se llama es cuando la máquina virtual carga la clase.

Vincent Ramdhanie
fuente
6

cuando un desarrollador usa un bloque de inicializador, el compilador de Java copia el inicializador en cada constructor de la clase actual.

Ejemplo:

el siguiente código:

class MyClass {

    private int myField = 3;
    {
        myField = myField + 2;
        //myField is worth 5 for all instance
    }

    public MyClass() {
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

es equivalente a:

class MyClass {

    private int myField = 3;

    public MyClass() {
        myField = myField + 2;
        myField = myField * 4;
        //myField is worth 20 for all instance initialized with this construtor
    }

    public MyClass(int _myParam) {
        myField = myField + 2;
        if (_myParam > 0) {
            myField = myField * 4;
            //myField is worth 20 for all instance initialized with this construtor
            //if _myParam is greater than 0
        } else {
            myField = myField + 5;
            //myField is worth 10 for all instance initialized with this construtor
            //if _myParam is lower than 0 or if _myParam is worth 0
        }
    }

    public void setMyField(int _myField) {
        myField = _myField;
    }


    public int getMyField() {
        return myField;
    }
}

public class MainClass{

    public static void main(String[] args) {
        MyClass myFirstInstance_ = new MyClass();
        System.out.println(myFirstInstance_.getMyField());//20
        MyClass mySecondInstance_ = new MyClass(1);
        System.out.println(mySecondInstance_.getMyField());//20
        MyClass myThirdInstance_ = new MyClass(-1);
        System.out.println(myThirdInstance_.getMyField());//10
    }
}

Espero que los desarrolladores entiendan mi ejemplo.

cardman
fuente
4

El bloque de código estático se puede usar para crear instancias o inicializar variables de clase (a diferencia de las variables de objeto). Por lo tanto, declarar "a" estático significa que es solo uno compartido por todos los objetos de prueba, y el bloque de código estático inicializa "a" solo una vez, cuando la clase de prueba se carga por primera vez, sin importar cuántos objetos de prueba se creen.

Paul Tomblin
fuente
Como seguimiento, si no creo una instancia del objeto sino que llamo una función estática pública. ¿Implica que este bloque está garantizado para ejecutarse antes de esta llamada a función pública? Gracias.
Szere Dyeri
Si llama a una función pública estática de la clase, entonces la clase debe cargarse primero, por lo que sí, el inicializador estático se ejecutará primero.
Paul Tomblin
A menos que sea la inicialización de clase la que (indirectamente) llamó al código que está tratando de usarlo. IFYSWIM. Dependencias circulares y todo eso.
Tom Hawtin - tackline
1
@Tom tiene razón: es posible escribir algo donde un inicializador estático llama a un método estático antes de que se llame a otro inicializador estático, pero mi mente retrocede ante el pensamiento, por lo que nunca lo consideré.
Paul Tomblin