una tipos tener sirve que puede para herencia ejemplos cuantos constructores comando clase java constructor initializer static-initializer initialization-block

tipos - Uso de inicializadores vs constructores en Java



this en java (9)

Así que he estado revisando mis habilidades Java últimamente y he encontrado algunos bits de funcionalidad que no conocía anteriormente. Inicializadores estáticos y de instancia son dos de esas técnicas.

Mi pregunta es cuándo uno 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 las variables estáticas / de instancia "finales" 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 bloque de código "if (someStaticVar == null) // do stuff" al comienzo de cada constructor

En ambos casos se supone que el código requerido para establecer estas variables es más complejo que simplemente "var = value", ya que de lo contrario no parece haber ninguna razón para usar un inicializador en lugar de simplemente establecer el valor al declarar 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 ciertamente 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 utilizar un inicializador porque no lo bloquea para que se ejecute ese código al escribir un nuevo constructor.

¿Me estoy perdiendo de algo? ¿Hay alguna otra situación en la que se debe usar un inicializador? ¿O es realmente una herramienta bastante limitada para ser utilizada en situaciones muy específicas?


Como mencionaste, no es útil en muchos casos y, como con cualquier sintaxis menos utilizada, probablemente quieras evitarla solo para evitar que la siguiente persona que vea tu código pase los 30 segundos para sacarla de las bóvedas.

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

Las variables estáticas en sí mismas deben evitarse de alguna manera, no siempre, pero si usa muchas de ellas, o usa mucho en una clase, puede encontrar diferentes enfoques, su yo futuro se lo agradecerá.


Con mucha frecuencia utilizo bloques de inicializadores estáticos para configurar los datos estáticos finales, especialmente las 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 ordenada, especialmente cuando los elementos no son triviales para inicializarse.

Una implementación ingenua tampoco puede crear una lista no modificable, que es un posible error. Lo anterior crea una estructura de datos inmutable que puede regresar felizmente de los métodos públicos y demás.


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

Los bloques de inicializador son miembros de la clase / objeto, mientras que los constructores no lo son . Esto es importante al considerar la extensión / subclasificación :

  1. Los inicializadores son heredados por subclases. (Aunque, puede ser sombreado)
    Esto significa que básicamente está garantizado que las subclases se inicializan según lo previsto por la clase padre.
  2. Los constructores no son heredados , sin embargo. (Solo llaman a super() [es decir, sin parámetros] implícitamente o tienes que hacer una llamada super(...) específica manualmente).
    Esto significa que es posible que una llamada super(...) implícita o explícita no pueda inicializar la subclase según lo previsto por la clase padre.

Considere este ejemplo de un bloque de inicializador:

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:
initializing in initializer block of: ChildOfParentWithInitializer init
-> No importa qué constructores implemente la subclase, el campo se inicializará.

Ahora considere este ejemplo con 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:
Constructor of ChildOfParentWithConstructor inits to null null
-> Esto inicializará el campo para null por defecto, aunque podría no ser el resultado que deseaba.


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


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 agregar esta pequeña demostración como un comentario. Para probar 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


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

Encuentro "if (someStaticVar == null) // hacer cosas" para que sea desordenado y propenso a errores. Si se inicializa estáticamente y se declara final , entonces 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 las variables estáticas / de instancia "finales" mientras que un constructor no puede

Supongo que estás diciendo las dos cosas:

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

y tienes razón en el primer punto, mal 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 los constructores, una de las mejores formas de manejar esto es encadenar constructores, proporcionando los valores predeterminados. Esto hace que quede 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; } }


Solo para agregar a algunos puntos ya excelentes aquí. El inicializador estático es seguro para subprocesos. Se ejecuta cuando la clase está cargada, y por lo tanto hace una inicialización de datos estáticos 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 realmente lo 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 olvide, ahora debe sincronizar en la clase, no en el nivel de instancia. Esto supone un costo por cada instancia construida en lugar de un costo de una vez cuando se carga la clase. Además, es feo ;-)


También me gustaría agregar un punto junto con todas las respuestas fabulosas anteriores. Cuando cargamos un controlador en JDBC utilizando Class.forName (""), se produce la carga de clase y se desencadena el inicializador estático de la clase Driver y el código dentro de él registra Driver to Driver Manager. Este es uno de los usos significativos del bloque de código estático.


Un inicializador estático es el equivalente de un constructor en el contexto estático. Sin duda lo verá con más frecuencia que un inicializador de instancias. A veces necesita ejecutar código para configurar el entorno estático.

En general, un iniciador de instancias 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 alguna lógica que es complicada de encadenar entre constructores (digamos que está subclasificando y no puede llamar a esto () porque necesita llamar a super ()), puede evitar la duplicación haciendo las cosas comunes en la instancia initalizador. Sin embargo, los iniciadores de instancias son tan raros que son una sintaxis sorprendente para muchos, así que los evito y prefiero que mi clase sea concreta y no anónima si necesito el comportamiento del constructor.

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