¿Debo usar bloques inicializadores en Java?

16

Recientemente me encontré con una construcción Java que nunca había visto antes y me preguntaba si debería usarla. Parece que se llama bloques inicializadores .

public class Test {
  public Test() { /* first constructor */ }
  public Test(String s) { /* second constructor */ }

  // Non-static initializer block - copied into every constructor:
  {
    doStuff();
  }
}

El bloque de código se copiará en cada constructor, es decir, si tiene un constructor múltiple, no tiene que volver a escribir el código.

Sin embargo, veo tres inconvenientes principales con esta sintaxis:

  1. Es uno de los pocos casos en Java donde el orden de su código es importante, ya que puede definir múltiples bloques de código y se ejecutarán en el orden en que se escriben. Esto me parece dañino, ya que simplemente cambiar el orden de los bloques de código cambiará el código.
  2. Realmente no veo ningún beneficio al usarlo. En la mayoría de los casos, los constructores se llamarán entre sí con algunos valores predefinidos. Incluso si este no es el caso, el código podría simplemente ser puesto en un método privado y llamado desde cada constructor.
  3. Reduce la legibilidad, ya que podría poner el bloque al final de la clase y el constructor normalmente está al comienzo de la clase. Es bastante contra-intuitivo mirar una parte completamente diferente de un archivo de código si no espera que sea necesario.

Si mis afirmaciones anteriores son ciertas, ¿por qué (y cuándo) se introdujo este constructo de lenguaje? ¿Hay algún caso de uso legítimo?

Reinstalar a Monica - dirkk
fuente
3
El ejemplo que ha publicado no incluye nada que parezca un bloque inicializador.
Simon B
66
@SimonBarker vuelve a mirar: el { doStuff(); }nivel de clase es un bloque inicializador.
amon
@SimonBarker El bloque de código que lo rodeadoStuff()
Reinstale a Monica - dirkk
2
"[S] implica cambiar el orden de los bloques de código en realidad cambiará el código". ¿Y en qué se diferencia eso de cambiar el orden de los inicializadores variables o las líneas de código individuales? Si no hay dependencias, no se produce ningún daño, y si hay dependencias, entonces poner las dependencias fuera de orden es lo mismo que ordenar las dependencias para líneas de código individuales. El hecho de que Java le permita referirse a métodos y clases antes de que se definan no significa que el código dependiente del orden sea raro en Java.
JAB

Respuestas:

20

Hay dos casos en los que uso bloques inicializadores.

El primero es para inicializar miembros finales. En Java, puede inicializar un miembro final ya sea en línea con la declaración, o puede inicializarlo en el constructor. En un método, está prohibido asignar a un miembro final.

Esto es valido:

final int val = 2;

Esto también es válido:

final int val;

MyClass() {
    val = 2;
}

Esto no es válido:

final int val;

MyClass() {
    init();
}

void init() {
    val = 2;  // cannot assign to 'final' field in a method
}

Si tiene varios constructores, y si no puede inicializar un miembro final en línea (porque la lógica de inicialización es demasiado compleja), o si los constructores no pueden llamarse a sí mismos, puede copiar / pegar el código de inicialización, o puede usar Un bloque inicializador.

final int val;
final int squareVal;

MyClass(int v, String s) {
    this.val = v;
    this.s = s;
}

MyClass(Point p, long id) {
    this.val = p.x;
    this.id = id;
}

{
    squareVal = val * val;
}

El otro caso de uso que tengo para los bloques de inicialización es para construir pequeñas estructuras de datos auxiliares. Declaro un miembro y pongo valores justo después de sus declaraciones en su propio bloque inicializador.

private Map<String, String> days = new HashMap<String, String>();
{
    days.put("mon", "monday");
    days.put("tue", "tuesday");
    days.put("wed", "wednesday");
    days.put("thu", "thursday");
    days.put("fri", "friday");
    days.put("sat", "saturday");
    days.put("sun", "sunday");
}
barjak
fuente
No es la llamada al método que no es válida. Es el código dentro del método init que no es válido. Solo los constructores y los bloques de inicialización pueden asignar a una variable miembro final, por lo tanto, la asignación en init no se compilará.
barjak
Su cuarto bloque de código no se compila. Los bloques inicializadores se ejecutan antes que todos los constructores, por squareVal = val * vallo tanto, se quejarán de acceder a valores no inicializados. Los bloques de inicialización no pueden depender de ningún argumento pasado al constructor. La solución habitual que he visto para ese tipo de problema es definir un único constructor "base" con la lógica compleja, y definir todos los demás constructores en términos de ese. La mayoría de los usos de los inicializadores de instancia, de hecho, se pueden reemplazar con ese patrón.
Malnormalulo
11

En general, no use bloques de inicializador no estáticos (y tal vez evite también los estáticos).

Sintaxis confusa

Mirando esta pregunta, hay 3 respuestas, pero engañaste a 4 personas con esta sintaxis. ¡Fui uno de ellos y llevo 16 años escribiendo Java! Claramente, la sintaxis es potencialmente propensa a errores. Me mantendría alejado de eso.

Constructores telescópicos

Para cosas realmente simples, puede usar constructores "telescópicos" para evitar esta confusión:

public class Test {
    private String something;

    // Default constructor does some things
    public Test() { doStuff(); }

    // Other constructors call the default constructor
    public Test(String s) {
        this(); // Call default constructor
        something = s;
    }
}

Patrón de constructor

Si necesita hacerStuff () al final de cada constructor u otra inicialización sofisticada, quizás un patrón de construcción sería lo mejor. Josh Bloch enumera varias razones por las cuales los constructores son una buena idea. Los constructores toman un poco de tiempo para escribir, pero si están escritos correctamente, son un placer usarlos.

public class Test {
    // Value can be final (immutable)
    private final String something;

    // Private constructor.
    private Test(String s) { something = s; }

    // Static method to get a builder
    public static Builder builder() { return new Builder(); }

    // builder class accumulates values until a valid Test object can be created. 
    private static class Builder {
        private String tempSomething;
        public Builder something(String s) {
            tempSomething = s;
            return this;
        }
        // This is our factory method for a Test class.
        public Test build() {
            Test t = new Test(tempSomething);
            // Here we do your extra initialization after the
            // Test class has been created.
            doStuff();
            // Return a valid, potentially immutable Test object.
            return t;
        }
    }
}

// Now you can call:
Test t = Test.builder()
             .setString("Utini!")
             .build();

Bucles de inicializador estático

Solía ​​usar mucho los inicializadores estáticos , pero ocasionalmente me topé con bucles donde 2 clases dependían de los bloques de inicializadores estáticos que se llamaban antes de que la clase pudiera cargarse por completo. Esto produjo un "error al cargar la clase" o un mensaje de error similarmente vago. Tuve que comparar archivos con la última versión de trabajo conocida en control de código fuente para descubrir cuál era el problema. No es divertido en absoluto.

Inicialización perezosa

Tal vez los inicializadores estáticos son buenos por razones de rendimiento cuando funcionan y no son demasiado confusos. Pero en general, prefiero la inicialización diferida a los inicializadores estáticos en estos días. Está claro lo que hacen, aún no me he encontrado con un error de carga de clase con ellos, y funcionan en más situaciones de inicialización que los bloques de inicializador.

Definición de datos

En lugar de la inicialización estática para construir estructuras de datos (compárese con los ejemplos en las otras respuestas), ahora uso las funciones auxiliares de definición de datos inmutables de Paguro :

private ImMap<String,String> days =
        map(tup("mon", "monday"),
            tup("tue", "tuesday"),
            tup("wed", "wednesday"),
            tup("thu", "thursday"),
            tup("fri", "friday"),
            tup("sat", "saturday"),
            tup("sun", "sunday"));

Conculsion

Al principio de Java, los bloques de inicialización eran la única forma de hacer algunas cosas, pero ahora son confusos, propensos a errores y, en la mayoría de los casos, han sido reemplazados por mejores alternativas (detalladas anteriormente). Es interesante saber acerca de los bloques de inicializador en caso de que los vea en el código heredado, o que salgan en una prueba, pero si estuviera haciendo una revisión del código y vi uno en el nuevo código, le pediría que justifique por qué ninguno de los las alternativas anteriores eran adecuadas antes de darle el visto bueno a su código.

GlenPeterson
fuente
3

Además de la inicialización de una variable de instancia que se declara como final(ver la respuesta de barjak ), también mencionaría el staticbloque de inicialización.

Puede usarlos como una especie de "constructor estático".

De esa manera, puede hacer inicializaciones complejas en una variable estática la primera vez que se hace referencia a la clase.

Aquí hay un ejemplo inspirado en el de barjak:

public class dayHelper(){
    private static Map<String, String> days = new HashMap<String, String>();
    static {
        days.put("mon", "monday");
        days.put("tue", "tuesday");
        days.put("wed", "wednesday");
        days.put("thu", "thursday");
        days.put("fri", "friday");
        days.put("sat", "saturday");
        days.put("sun", "sunday");
    }
    public static String getLongName(String shortName){
         return days.get(shortName);
    }
}
C.Champagne
fuente
1

Por lo que respecta a los bloques de inicializador no estáticos, su función básica es actuar como un constructor predeterminado en clases anónimas. Ese es básicamente su único derecho a existir.

Nico
fuente
0

Estoy totalmente de acuerdo con las afirmaciones 1, 2, 3. Tampoco uso nunca inicializadores de bloque por estos motivos y no sé por qué existe en Java.

Sin embargo, me veo obligado a usar el inicializador de bloque estático en un caso: cuando tengo que instanciar un campo estático cuyo constructor puede lanzar una excepción marcada.

private static final JAXBContext context = JAXBContext.newInstance(Foo.class); //doesn't compile

Pero en cambio tienes que hacer:

private static JAXBContext context;
static {
    try
    {
        context = JAXBContext.newInstance(Foo.class);
    }
    catch (JAXBException e)
    {
        //seriously...
    }
}

Este lenguaje me parece muy feo (también evita que lo marques contextcomo final), pero esta es la única forma admitida por Java para inicializar dichos campos.

Manchado
fuente
Creo que si establece context = null;en su bloque catch, es posible que pueda declarar el contexto como final.
GlenPeterson
@GlenPeterson Lo intenté pero no se compila:The final field context may already have been assigned
Visto el
¡Uy! Apuesto a que puedes hacer que tu contexto sea final si introduces una variable local dentro del bloque estático:static { JAXBContext tempCtx = null; try { tempCtx = JAXBContext.newInstance(Foo.class); } catch (JAXBException ignored) { ; } context = tempCtx; }
GlenPeterson