76

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.Uso de inicializadores vs constructores en Java

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:

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

  • inicializadores estáticos se puede utilizar para establecer el valor de las variables estáticas en una clase, que debería ser más eficiente que tener un "si (someStaticVar == null) // hacer cosas" bloque de código en el inicio de cada constructor

Tanto de estos casos se supone que el código requiere d 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, si bien estas ganancias no son triviales (especialmente la capacidad de establecer una variable final), parece que hay un número bastante limitado de situaciones en las que se debe utilizar un inicializador.

Ciertamente, puede utilizar 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.

¿Echo de menos 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?

+0

Desde inicializadores de instancia son una característica poco conocida, aquí está un ejemplo para ayudar a los lectores: 'privada final int somevar; {somevar = 2;} '(nota, sin constructor.) Para más diversión, busque" inicialización de doble llave "(corte de sintaxis). –

Respuesta

47

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 va a inicializar cuando se carga la clase, un inicializador estático es el camino a seguir, especialmente porque le permite realizar una inicialización compleja y aún 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 dice:

inicializadores estáticos/instancia se pueden utilizar para establecer el valor de las variables "finales" estática/Instancia, mientras que un constructor no puede

Supongo que está diciendo que ambos:

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

y que son correctos en el primer punto, el mal en el segundo. Se puede, por ejemplo, hacer lo siguiente:

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

Además, cuando una gran cantidad de código se comparte entre los constructores, una de las mejores maneras de manejar esto es para los constructores de la cadena, que proporciona los valores por defecto. 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; 
    } 
} 
+2

Eso era lo que estaba diciendo que sí. Tenía en mente que las finales tenían que configurarse cuando se declaraban, en lugar de solo poder establecerse una vez. Es una idea un poco tonta cuando lo pienso, pero estaba en mi cabeza, no obstante. Gracias por aclarar eso. – Inertiatic

+0

Olvidé agregar el bit sobre el encadenamiento de constructores, así que simplemente lo agregué. – Eddie

+0

En mi humilde opinión, los inicializadores de instancia solo se "copian" en el constructor, por lo tanto, pueden hacer lo mismo que el código de constructor, son código de constructor, aunque se separan visualmente. –

2

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 todos modos, no siempre, pero si usa muchas de ellas, o usa mucho en una clase, puede encontrar diferentes enfoques, su yo futuro se lo agradecerá.

23

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 ordenado, en particular cuando los artículos no son triviales para inicializar.

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.

+4

No me gusta su ejemplo específico porque es mucho más adecuado para la implementación como 'enum'. – JAB

+14

Luego cambie SUITS a PAST_GIRLFRIENDS o algo más. Pero tienes razón, ya que la probabilidad de que el conjunto de cartas clásico cambie es cero, las enumeraciones serían más ... adecuadas. – mike

42

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.

7

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 instancia es mejor para las clases internas anónimas. Eche un vistazo al JMock's cookbook 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 para encadenar constructores (digamos que está subclasificando y no puede llamar a esto() porque necesita llamar a super()), puede evitar la duplicación haciendo lo común cosas en el initalizador de instancia. 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 utilizar el marco.

13

Solo para agregar algunos puntos que ya son 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")); 
     } 
    } 

frente

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 se olvide, que ahora tienen que sincronizar en la clase, no a 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 ;-)

+0

entonces si user.prop se crea después de que la clase se ha utilizado por primera vez, nunca se tendrá en cuenta o ¿será después de la compilación? (para el init estático) – Ced

3

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.

11

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 
+0

¡Exactamente el ejemplo que estaba buscando! Hola Daniel, gracias por el ex. Solo tiene una pregunta: ¿Por qué se ejecutó "A ctor" primero? Predijo "B initX, A ctor, B ctor". Además, parece que sabes muy bien el idioma, ¿estarías de acuerdo? – Cody

+0

Un codificador se ejecuta primero porque para hacer una B primero debe tener una A. No estoy seguro de qué tan bien conozco el idioma, ya que mi cerebro se niega a aprender C++ y Java mejor de lo que ya los conoce porque estos idiomas no son tan buenos diseñado para que las cosas que no sé a menudo tengan el sabor de las heces sobrantes en el fondo de un vaso: amargas. – Daniel

+2

Si amplía este ejemplo mediante bloques de inicialización y bloques de inicialización estáticos, sería aún más útil. –

Cuestiones relacionadas