Pregunta

Así que últimamente he mejorado mis conocimientos de Java y he encontrado algunas funcionalidades que no conocía anteriormente. Los inicializadores estáticos y de instancia son dos de estas técnicas.

Mi pregunta es ¿cuándo se usaría un inicializador en lugar de incluir el código en un constructor? He pensado en un par de posibilidades obvias:

  • los inicializadores estáticos / de instancia se pueden usar para establecer el valor de " final " variables estáticas / instancia mientras que un constructor no puede

  • los inicializadores estáticos se pueden usar para establecer el valor de cualquier variable estática en una clase, que debería ser más eficiente que tener un " if (someStaticVar == null) // do stuff " bloque de código al inicio de cada constructor

Ambos casos suponen que el código requerido para configurar estas variables es más complejo que simplemente "var = valor", ya que de lo contrario no parece haber ninguna razón para usar un inicializador en lugar de simplemente establecer el valor cuando declarando la variable.

Sin embargo, aunque estas no son ganancias triviales (especialmente la capacidad de establecer una variable final), parece que hay un número bastante limitado de situaciones en las que se debe usar un inicializador.

Uno puede usar un inicializador para mucho de lo que se hace en un constructor, pero realmente no veo la razón para hacerlo. Incluso si todos los constructores de una clase comparten una gran cantidad de código, el uso de una función de inicialización privada () parece tener más sentido para mí que usar un inicializador porque no te obliga a hacer que ese código se ejecute al escribir un nuevo constructor.

¿Me estoy perdiendo algo? ¿Existen otras situaciones en las que se debe usar un inicializador? ¿O es realmente solo una herramienta bastante limitada para ser usada en situaciones muy específicas?

¿Fue útil?

Solución

Los inicializadores estáticos son útiles como se menciona en cletus y los uso de la misma manera. Si tiene una variable estática que se inicializará cuando se cargue la clase, entonces hay un inicializador estático, especialmente porque le permite realizar una inicialización compleja y aún así tener la variable estática siendo final . Esta es una gran victoria.

Encuentro " if (someStaticVar == null) // do stuff " ser desordenado y propenso a errores. Si se inicializa estáticamente y se declara final , se evita la posibilidad de que sea null .

Sin embargo, estoy confundido cuando dices:

  

los inicializadores estáticos / de instancia se pueden usar para establecer el valor de " final "   variables estáticas / instancia mientras que un constructor no puede

Supongo que estás diciendo ambas cosas:

  • los inicializadores estáticos se pueden usar para establecer el valor de " final " variables estáticas mientras que un constructor no puede
  • los inicializadores de instancia se pueden usar para establecer el valor de " final " variables de instancia mientras que un constructor no puede

y tienes razón en el primer punto, incorrecto en el segundo. Puedes, por ejemplo, hacer esto:

class MyClass {
    private final int counter;
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

Además, cuando se comparte una gran cantidad de código entre constructores, una de las mejores maneras de manejar esto es encadenar constructores, proporcionando los valores predeterminados. Esto deja bastante claro lo que se está haciendo:

class MyClass {
    private final int counter;
    public MyClass() {
        this(0);
    }
    public MyClass(final int counter) {
        this.counter = counter;
    }
}

Otros consejos

Las clases internas anónimas no pueden tener un constructor (ya que son anónimos), por lo que son un ajuste bastante natural para los inicializadores de instancia.

La mayoría de las veces uso bloques de inicialización estática para configurar datos estáticos finales, especialmente colecciones. Por ejemplo:

public class Deck {
  private final static List<String> SUITS;

  static {
    List<String> list = new ArrayList<String>();
    list.add("Clubs");
    list.add("Spades");
    list.add("Hearts");
    list.add("Diamonds");
    SUITS = Collections.unmodifiableList(list);
  }

  ...
}

Ahora este ejemplo se puede hacer con una sola línea de código:

private final static List<String> SUITS =
  Collections.unmodifiableList(
    Arrays.asList("Clubs", "Spades", "Hearts", "Diamonds")
  );

pero la versión estática puede ser mucho más nítida, especialmente cuando los elementos no son triviales para inicializar.

Una implementación ingenua tampoco puede crear una lista no modificable, lo que es un error potencial. Lo anterior crea una estructura de datos inmutable que puede devolver fácilmente de los métodos públicos, etc.

Solo para agregar algunos puntos ya excelentes aquí. El inicializador estático es seguro para subprocesos. Se ejecuta cuando se carga la clase y, por lo tanto, hace que la inicialización de datos estáticos sea más simple que usar un constructor, en el que necesitaría un bloque sincronizado para verificar si los datos estáticos se inicializan y luego se inicializan.

public class MyClass {

    static private Properties propTable;

    static
    {
        try 
        {
            propTable.load(new FileInputStream("/data/user.prop"));
        } 
        catch (Exception e) 
        {
            propTable.put("user", System.getProperty("user"));
            propTable.put("password", System.getProperty("password"));
        }
    }

versus

public class MyClass 
{
    public MyClass()
    {
        synchronized (MyClass.class) 
        {
            if (propTable == null)
            {
                try 
                {
                    propTable.load(new FileInputStream("/data/user.prop"));
                } 
                catch (Exception e) 
                {
                    propTable.put("user", System.getProperty("user"));
                    propTable.put("password", System.getProperty("password"));
                }
            }
        }
    }

No lo olvides, ahora debes sincronizar en la clase, no en el nivel de instancia. Esto incurre en un costo por cada instancia construida en lugar de un costo único cuando se carga la clase. Además, es feo ;-)

Leí un artículo completo en busca de una respuesta al orden de inicio de los inicializadores frente a sus constructores. No lo encontré, así que escribí un código para verificar mi comprensión. Pensé en añadir esta pequeña demostración como comentario. Para comprobar su comprensión, vea si puede predecir la respuesta antes de leerla en la parte inferior.

/**
 * Demonstrate order of initialization in Java.
 * @author Daniel S. Wilkerson
 */
public class CtorOrder {
  public static void main(String[] args) {
    B a = new B();
  }
}

class A {
  A() {
    System.out.println("A ctor");
  }
}

class B extends A {

  int x = initX();

  int initX() {
    System.out.println("B initX");
    return 1;
  }

  B() {
    super();
    System.out.println("B ctor");
  }

}

Salida:

java CtorOrder
A ctor
B initX
B ctor

Un inicializador estático es el equivalente de un constructor en el contexto estático. Ciertamente lo verás más a menudo que un inicializador de instancia. A veces es necesario ejecutar código para configurar el entorno estático.

En general, una instancia de initalizer es mejor para clases internas anónimas. Eche un vistazo al libro de cocina de JMock para ver una forma innovadora de usarlo para hacer que el código sea más legible.

A veces, si tiene una lógica complicada de encadenar entre constructores (digamos que está creando subclases y no puede llamar a esto () porque necesita llamar a super ()), podría evitar la duplicación haciendo cosas comunes en el caso de initalizer. Sin embargo, los inicializadores de instancias son tan raros que son una sintaxis sorprendente para muchos, por lo que los evito y prefiero hacer mi clase concreta y no anónima si necesito el comportamiento del constructor.

JMock es una excepción, porque así es como se pretende utilizar el marco.

Hay un aspecto importante que debe considerar en su elección:

Los bloques de inicialización son miembros de la clase / objeto, mientras que los constructores no son . Esto es importante cuando se considera extensión / subclase :

  1. Los inicializadores se heredan por subclases. (Aunque, puede ser sombreado)
    Esto significa que básicamente se garantiza que las subclases se inicialicen según lo previsto por la clase principal.
  2. Sin embargo, los constructores no se heredan . (Solo llaman implícitamente a super () [es decir, sin parámetros] o tienes que hacer una llamada específica a super (...) manualmente.)
    Esto significa que es posible que una llamada implícita o exclusiva super (...) no inicialice la subclase como pretendía la clase principal.

Considere este ejemplo de un bloque de inicialización:

class ParentWithInitializer {
    protected final String aFieldToInitialize;

    {
        aFieldToInitialize = "init";
        System.out.println("initializing in initializer block of: " 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithInitializer extends ParentWithInitializer{
    public static void main(String... args){
        System.out.println(new ChildOfParentWithInitializer().aFieldToInitialize);
    }
}

salida:
inicializando en el bloque inicializador de: ChildOfParentWithInitializer init
- > No importa qué constructores implemente la subclase, el campo se inicializará.

Ahora considere este ejemplo con los constructores:

class ParentWithConstructor {
    protected final String aFieldToInitialize;

    // different constructors initialize the value differently:
    ParentWithConstructor(){
        //init a null object
        aFieldToInitialize = null;
        System.out.println("Constructor of " 
            + this.getClass().getSimpleName() + " inits to null");
    }

    ParentWithConstructor(String... params) {
        //init all fields to intended values
        aFieldToInitialize = "intended init Value";
        System.out.println("initializing in parameterized constructor of:" 
            + this.getClass().getSimpleName());
    }
}

class ChildOfParentWithConstructor extends ParentWithConstructor{
    public static void main (String... args){
        System.out.println(new ChildOfParentWithConstructor().aFieldToInitialize);
    }
}

salida:
El constructor de ChildOfParentWithConstructor está en nulo nulo
- > Esto inicializará el campo a null de forma predeterminada, aunque es posible que no sea el resultado deseado.

También me gustaría agregar un punto junto con todas las respuestas fabulosas anteriores. Cuando cargamos un controlador en JDBC usando Class.forName (" "), la carga de la clase ocurre y el inicializador estático de la clase del controlador se activa y el código que contiene inscribe el controlador en el Administrador del controlador. Este es uno de los usos significativos del bloque de código estático.

Como mencionó, no es útil en muchos casos y como con cualquier sintaxis menos utilizada, es probable que desee evitarlo solo para evitar que la siguiente persona que vea su código dedique los 30 segundos a retirarlo. las bóvedas.

Por otro lado, es la única forma de hacer algunas cosas (creo que las cubriste bastante).

Las variables estáticas en sí mismas deben evitarse de alguna manera, no siempre, pero si usas muchas o usas muchas en una clase, es posible que encuentres diferentes enfoques, tu futuro te lo agradecerá.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top