¿Debo usar bloques de inicialización en Java?

14

Hace poco me encontré con una construcción Java que nunca había visto antes y me preguntaba si debería usarla. Parece que se llaman bloques de inicialización .

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 varios constructores, 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 están escritos. Esto me parece perjudicial, 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 ponerse en un método privado y ser llamado por cada constructor.
  3. Reduce la legibilidad, ya que podría poner el bloque al final de la clase y el constructor está normalmente al principio de la clase. Es bastante contraintuitivo ver una parte completamente diferente de un archivo de código si no espera que sea necesario.

Si mis afirmaciones anteriores son verdaderas, ¿por qué (y cuándo) se introdujo esta construcción de lenguaje? ¿Existen casos de uso legítimos?

    
pregunta dirkk 12.05.2014 - 16:50

5 respuestas

20

Hay dos casos en los que uso bloques de inicialización.

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 asignarlo a un miembro final.

Esto es válido:

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 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, entonces puede copiar / pegar el código de inicialización, o puedes usar un bloque de inicialización.

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 de ayuda. Declaro un miembro y pongo valores en él 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");
}
    
respondido por el barjak 12.05.2014 - 17:27
10

En general, no use bloques de inicialización no estáticos (y quizás evite los estáticos también).

Sintaxis confusa

Mirando esta pregunta, hay 3 respuestas, sin embargo, engañó 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 ello.

Constructores telescópicos

Para cosas realmente simples, puedes 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 del generador

Si necesita hacer cosas () al final de cada constructor u otra inicialización sofisticada, tal vez lo mejor sería un patrón de construcción. Josh Bloch enumera varias razones por las que los constructores son una buena idea. Los constructores toman un poco de tiempo para escribir, pero correctamente escritos, es un placer utilizarlos.

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 inicialización estáticos

Solía usar mucho los inicializadores estáticos , pero ocasionalmente me topaba con bucles en los que 2 clases dependían de los bloques de inicialización 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 inicialización perezosa a los inicializadores estáticos en estos días. Está claro lo que hacen, todavía no me he encontrado con un error de carga de clase, y funcionan en más situaciones de inicialización que los bloques de inicialización.

Definición de datos

En lugar de la inicialización estática para construir estructuras de datos (compare con los ejemplos de las otras respuestas), ahora uso las funciones de ayuda para la 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"));

Conculsión

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 inicialización en caso de que los vea en el código heredado, o que aparezcan en una prueba, pero si estuviera haciendo una revisión del código y viera uno en el código nuevo, le pediría que justifique por qué ninguno de los Las alternativas anteriores eran adecuadas antes de dar el visto bueno a su código.

    
respondido por el GlenPeterson 24.06.2016 - 21:15
3

Además de la inicialización de una variable de instancia que se declara como final (consulte la respuesta de barjak ) También mencionaría el bloque de inicialización static .

Puedes usarlos como tipo de "contructor estático".

De esa manera, puedes 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);
    }
}
    
respondido por el C.Champagne 12.05.2014 - 17:44
1

Como se refiere a los bloques de inicialización 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.

    
respondido por el Nico 24.06.2016 - 20:28
0

Estoy totalmente de acuerdo con las afirmaciones 1, 2, 3. Tampoco uso 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 crear una instancia de 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 idioma me parece muy feo (también impide que marques context como final ) pero esta es la única forma en que Java puede inicializar dichos campos.

    
respondido por el Spotted 25.06.2016 - 09:08

Lea otras preguntas en las etiquetas