style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">

Inicializadores (Inicializadores de Instancia)

Continuando con el post anterior sobre bloques estáticos, pero ahora toca definir los usos, ventajas y desventajas de los inicializadores tambíen conocidos como "inicializadores de instancia", como se puede deducir estos bloques están dedicados para colocar bloques de código para inicializar variables que pertenezcan a la instancia.

Los inicializadores de instancia son bloques de código que pertenecen a la instancia, y se declaran dentro de la clase entre llaves como a continuación se muestra en el siguiente código:

package com.ejemplos.inicializadores;

public class Ejemplo1 {

        {
                System.out.println(this.getClass().getSimpleName());
        }

        Ejemplo1() {
                System.out.println("Constructor");
        }

        public static void main(String[] args) {
                new Ejemplo1();
        }

        {              
                System.out.println(this.getClass().getName());
        }

        {
                System.out.println(this.getClass().getSuperclass());
        }

}

Salida:

Ejemplo1
com.ejemplos.inicializadores.Ejemplo1
class java.lang.Object

Constructor

Como se puede observar en la salida resultante del ejemplo anterior los inicializadores estáticos se ejecutan antes de la ejecución del constructor, por lo tanto estos inicializadores solo se ejecutan cuando se va crear una instancia, por lo cual se les llama inicializadores de instancia, cabe destacar que si la clase tiene varios constructores, el o los inicializador(es) se ejecutaran independientemente de que constructor se utilice para crear una instancia de la clase, veamos el siguiente ejemplo:

package com.ejemplos.inicializadores;

public class Ejemplo2 {

        {
                System.out.println(this.getClass().getSimpleName());
        }

        Ejemplo2() {
                System.out.println("Constructor");
        }

        Ejemplo2(String mensaje) {
                System.out.println("Constructor " + mensaje);
        }

        public static void main(String[] args) {
                            // Si se comentaran u omitieran las siguientes dos líneas de código no imprimiría nada por no crear ninguna instancia.
                new Ejemplo2();    
                new Ejemplo2("con mensaje");
        }

        {
                System.out.println(this.getClass().getName());
        }

}

Salida:

Ejemplo2
com.ejemplos.inicializadores.Ejemplo2

Constructor
Ejemplo2
com.ejemplos.inicializadores.Ejemplo2

Constructor con mensaje

Al igual que los inicializadores estáticos, los inicializadores de instancia ofrecen la ventaja de poder realizar calculos para la inicialización de variables y manejar excepciones si se presentan durante la inicialización como a continuación se presenta el siguiente ejemplo:

package com.ejemplos.inicializadores;

import java.util.Random;

public class Ejemplo3 {

        int numero;
        {
                try{
                        numero = lanzaDado();
                } catch (InvalidValueException e){
                        System.out.println(e.getMessage())
                }
        }

        Ejemplo3() {
                System.out.println(numero);
        }

        public static void main(String[] args) {
                new Ejemplo3();
        }

        public int lanzaDado() throws InvalidValueException {

                int n = new Random().nextInt(6);
                if (n == 0){
                        throw new InvalidValueException("Valor 0 es invalido");
                }
                return n;
        }

        class InvalidValueException extends Exception {

                InvalidValueException(String msg) {
                        super(msg);
                }
        }
}

Salida:

Valor 0 es invalido
0

Un último ejemplo muestra el uso de inicializadores de instancia en claes anónimas, y es que se puede tener la necesidad de inicializar alguna variable y como las clases anónimas no permiten definir constructores implicitamente, un inicializador de instancia es de mucha utilidad:

package com.ejemplos.inicializadores;

import java.util.InputMismatchException;
import java.util.Scanner;

public class Ejemplo3 {

        public static void main(String[] args) {

                new Thread(new Runnable() {

                        int numero;
                        {
                                System.out.println("Introduce un numero: ");
                                try{
                                        numero = new Scanner(System.in).nextInt();
                                } catch (InputMismatchException e){
                                        e.printStackTrace();
                                }
                        }

                        @Override
                        public void run() {
                                System.out.println("Numero introducido: " + numero);
                        }

                }).start();
        }
}

Salida:

Introduce un numero:
4
Numero introducido: 4

Ya para finalizar:

Usos comunes:

1.- Inicialización de variables o constantes de forma eager.
2.- Realizar calculos y/o asignaciones de variables de instancia.
3.- Colocar código que se necesite ejecutar al crear una instancia de una clase anónima.

Ventajas:

1.-Puedes ejecutar asignaciones a constantes y variables de instancia que requieran la ejecución de algún calculo sin la necesidad de crear una instancia.
2.-Si el código que utilizas para inicializar una constante o variable estática causa excepciones, las puedes manejar en bloques try-catch sin la necesidad de usar metodos estáticos o de instancia.
3.-En esta ocasión a diferencia de los inicializadores estáticos, los inicializadores de instancia pueden invocar a "super" y "this" por pertenecer a una instancia.

Limitaciones o desventajas:

1.-El tamaño de los inicializadores de instancia no debe exceder los 64kb.
2.-No se pueden lanzar excepciones checadas * (checa la sección de comentarios el de @jpaul)
3.-No se puede regresar ningun valor o tipo.

Y como siempre si en algo me equivoco o algo me falto pues no duden en comentar.

Saludos.

Comentarios

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.

Re: inicializadores

 

He aquí una “aplicación”.

De este código, tenemos el siguiente método:

public void play() {
    Timer timer1 = new Timer(0, evt1 -> {
        button.setText("Wait...");
        button.setEnabled(false);
        Icon icon = map.get(random.nextInt(map.size()) + 1);
        label.setIcon(icon);
        Timer timer2 = new Timer(1500, evt2 -> {
            label.setIcon(null);
            button.setText("Throw of the dice");
            button.setEnabled(true);
        });
        timer2.setRepeats(false);
        timer2.start();
    });
    timer1.setRepeats(false);
    timer1.start();
}

Si lo cambiamos para utilizar inicializadores no estáticos, quedaría de la siguiente manera:

public void play() {
    new Timer(0, evt1 -> {
       button.setText("Wait...");
       button.setEnabled(false);
       Icon icon = map.get(random.nextInt(map.size()) + 1);
       label.setIcon(icon);
       new Timer(1500, evt2 -> {
         label.setIcon(null);
         button.setText("Throw of the dice");
         button.setEnabled(true);
       }) {
         {
          setRepeats(false);
          start();
         }
       };
    }) {
       {
         setRepeats(false);
         start();
       }
    };
}

¿Y las variables, apá? ...

~~~

Imagen de Cid

Buen ejemplo

Me perdí un poco porque creí que el uso estaba en las lambdas, pero despues vi el segundo código que es en donde los aplicas y mandas a llamar a setRepeats y start.

"¿Y las variables, apá? ..."

Esta claro que no solo se usan para inicializar variables, si no para realizar tareas que inicialicen algún entorno que necesite el objeto.

Gracias por el ejemplo.

Saludos.

Orden

los inicializadores estáticos se ejecutan antes de la ejecución del constructor

Más precisamente:

  1. Se ejecuta el código correspondiente de la superclase.
  2. Se ejecutan los inicializadores no estáticos en el orden en que fueron declarados en la clase actual.
  3. Se ejecute el cuerpo del constructor correspondiente.

~~~

Imagen de Cid

correcto

package com.ejemplos.inicializadores;

class SuperEjemplo4 {

        SuperEjemplo4() {
                System.out.println("constructor SuperEjemplo4");
        }
}

public class Ejemplo4 extends SuperEjemplo4 {

        {
                System.out.println("Inicializador: " + this.getClass().getSimpleName());
        }

        public Ejemplo4() {
                System.out.println("constructor Ejemplo4");
        }

        public static void main(String[] args) {
                new Ejemplo4();
        }
}

Salida:

constructor SuperEjemplo4
Inicializador: Ejemplo4
constructor Ejemplo4

Re: excepciones checadas

No se pueden lanzar excepciones checadas.

es posible. Sin embargo, todos los contructores de la clase deben incluir tales excepciones en la clausula throws. Ejemplo:

/**
 * This is a test class.
 */

public class Snippet {

    {
       // checked exception: java.net.MalformedURLException
       new java.net.URL("https://www.google.com.mx");
    }

    /**
     * Creates an instance of {@code Snippet}.
     */

    public Snippet(String s) throws java.net.MalformedURLException, java.io.IOException {
       System.out.println("OK");
    }

    {
       // checked exception: java.io.IOException
       java.nio.file.Files.deleteIfExists(java.nio.file.Paths.get("settings.ini"));
    }

}

Si revisamos el bytecode con el comando "javap -c Snippet.class":

Compiled from "Snippet.java"
public class Snippet {
  public Snippet() throws java.net.MalformedURLException, java.io.IOException;
    Code:
       0: aload_0
       1: invokespecial #13                 // Method java/lang/Object."<init>":()V
       4: new           #15                 // class java/net/URL
       7: ldc           #17                 // String "https://www.google.com.mx"
       9: invokespecial #19                 // Method java/net/URL."<init>":(Ljava/lang/String;)V
      12: ldc           #22                 // String settings.ini
      14: iconst_0
      15: anewarray     #24                 // class java/lang/String
      18: invokestatic  #26                 // Method java/nio/file/Paths.get:(Ljava/lang/String;[Ljava/lang/String;)Ljava/nio/file/Path;
      21: invokestatic  #32                 // Method java/nio/file/Files.deleteIfExists:(Ljava/nio/file/Path;)Z
      24: pop
      25: getstatic     #38                 // Field java/lang/System.out:Ljava/io/PrintStream;
      28: ldc           #44                 // String OK
      30: invokevirtual #46                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      33: return
}

Encontraremos que el compilador hace un merge del código de los inicializadores no estáticos y de cada constructor, según corresponda.

~~~

Imagen de Cid

Muy bueno

No se si corregir o no el post original por aquellos que no leen los comentarios hace tiempo vi como interpretar el bytecode y tampoco hice mucho caso pero bueno por aquí dejo un link que les sea útil a los demas para entender lo que coloco @jpaul

bytecode

¿Confusas?

 

Adicionalmente, cuando éstas construcciones son pasadas por alguna herramienta de análisis estático de software, ésta podría quejarse amargamente. He aquí dos ejemplos de lo anterior:

  • PMD

    Esta herramienta tiene la siguiente regla:

    NonStaticInitializer

    A non-static initializer block will be called any time a constructor is invoked (just prior to invoking the constructor). While this is a valid language construct, it is rarely used and is confusing. (1)

    Es decir, que se trata de algo confuso.

  • SonarQube

    Esta herramienta tiene la siguiente regla:

    Non-static class initializers should not be used

    Non-static initializers are rarely used, and can be confusing for most developers. When possible, they should be refactored into standard constructors or field initializers. (2)

    Es decir, que muchos desarrolladores se podrían confundir. No especifica si principiantes o avanzados. Se recomienda sustituir esta construcción por su equivalente. Después de todo, esto mismo hace el compilador.

¿Raras? Sí, es cierto. Intento recordar ... ninguna API que las contenga viene a mi mente. Pero, -el meollo del asunto-, ¿son confusas estas construcciones? Ojalá la comunidad pudiera dar su opinión al respecto.


Notas

  1. http://bit.ly/1FD4ROq
  2. http://bit.ly/1IswZ4X

~~~

Imagen de ezamudio

sí lo son

Respecto de lo último que dices: no sabes ai alguna API las contiene, porque no aparece a nivel API. Si no tienes los fuentes de una biblioteca, no puedes saber si usan eso en su código o no, porque el compilador no lo conserva tal cual sino que lo refactoriza a constructores o pasa la inicialización a los campos correspondientes.

En mi opinión, sí son confusas, por varias razones. Imagina que te integras a un equipo que lleva rato trabajando en un proyecto:

De entrada, si no conoces bien la especificación completa del lenguaje, cuando te topes con ese código vas a pasar un buen rato rascándote la cabeza preguntándote qué hace, o vas a decidir que ese código es intocable y ps mejor le das la vuelta a ver cómo.

Si navegando en un IDE llegas a un método que usa un campo, y quieres saber cómo se inicializa ese campo, y vas a su declaración, te muestra que es null. Luego ves que no hay un setter. Y si el IDE no te muestra en la vista de estructura de manera clara los inicializadores de instancia, vas a terminar ejecutando la aplicación con debugger para ver dónde carajos se inicializa ese valor, lo cual te puede tomar bastante más tiempo que un simple análisis estático del código; en IntelliJ IDEA por ejempo dice "class initializer" y te muestra los campos inicializados, pero en Eclipse sólo salen bloques que dicen {...} y ya.

Aunque conozcas el lenguaje lo suficientemente bien, y encuentres estos bloques, si la clase es compleja y tiene varios cientos de líneas de código, y tienes que estar reacomodando esos bloques para que más o menos quede más legible el código... cuando llegues a ese punto, llegarás pronto a la conclusión de que lo mejor es un refactoring para eliminar dichos bloques, porque no ayudan en nada a la legibilidad del código. Y debes explicar eso en el mensaje del commit.

Lo que dice PMD se entendería incluso mejor así: it is rarely used and therefore confusing. Es decir, lo confuso viene de lo poco común de esa estructura; casi nadie la usa en producción (la evitan porque no ayuda en nada a la legibilidad del código y realmente hay maneras más limpias de lograr lo que puedes lograr con su uso), por lo tanto casi no lo ves en código productivo, y por lo mismo casi nadie de se acuerda de su existencia, si es que de entrada lo conocieron leyendo un libro o estudiando para su certificación (otra muestra de lo poco útil que son las certificaciones).

¿Por qué no ayuda en nada a la legibilidad? Porque su sintaxis es muy irregular, inconsistente con el resto del lenguaje. Y hoy en día que los programadores en vez de especializarse tanto en un lenguaje, prefieren conocer varios (lo cual me parece mejor para todos, honestamente), aunque no sea todos a fondo, o se especialicen un poco más en uno que no sea Java, no ayuda en nada esta estructura. Alguien que venga de Groovy por ejemplo lo ve y puede pensar que es un closure que no sirve para nada porque no fue asignado a nada y por lo tanto nunca se va a ejecutar y entonces termina borrando ese código así nomás.

Te lo pongo así de fácil: cuando yo vi este post la primera vez, me le quedé viendo un buen rato al código, y la verdad no me acordaba de estas cosas. Decidí que seguramente era código válido y se iba a ejecutar cada bloque de manera secuencial cada vez que se creara una nueva instancia, como si fueran parte del constructor, pero no sabía si antes o después del constructor. CASI le atino, y eso porque medio me acordé del inicializador estático... tiene más sentido que se ejecute antes, pero no me quedaba claro. Yo, con todos mis años de programar en Java, me tuve que poner a adivinar qué carajos haría ese código. ¿Hay algo más confuso que eso?

Imagen de Cid

Si

De hecho lo que menciona @ezamudio es correcto me puse a escarbar el uso de estas cosas porque cuando lei los manuales de la certificación pues los mencionaban sin decir los usos prácticos que es lo que siempre se pregunta la persona que toma el curso ¿Y esto para que me va servir ?, y como bien dicen jamás he oido a alguna persona que lleve varios años programando en Java utilizarlos, es más algunos ni los conocen, pero no se si soy terco pero ¿ para que los crearon si son tan confusos y no muy utilizados ?, bueno a los estáticos si les he visto una que otra vez pero a los de instancia no les he visto ni he oido que alguien los utilice, más que yo cuando daba cursos de Java, y en ejemplos de "hola mundo".

Pues haber quien aporta más al tema y nos dice si los a utilizado.

Saludos.

Imagen de ezamudio

porque es difícil

Diseñar un lenguaje es bastante difícil. Probablemente hubo una larga discusión respecto si meter esta funcionalidad o no, y al final decidieron dejarla en caso que alguien la llegue a necesitar porque alguien presentó un caso hipotético hipercomplicado donde era necesario (o al menos muy muy conveniente) tener esta estructura disponible.

Esa es la manera en que un lenguaje termina volviéndose complicadísimo; le meten y le meten y le meten cosas nomás porque sí...

Imagen de Cid

Ok

Rescatando un punto a favor, el uso en clases anónimas sería ese caso hipotético pues si necesitas inicializar una variable que pertenezca a la clase anónima y manejar un try-catch no puedes definir un nuevo constructor más que el que ya tiene por default no ? y en su lugar usas el inicializador de instancia para ello o estoy equivocado ?

Imagen de ezamudio

de acuerdo

En clases anónimas puede ser bastante útil; tal vez por eso lo dejaron. Pero entonces, debería sólo ser válido en clases anónimas...

en cuanto al try-catch, pues esa es la bronca de tener excepciones "checadas". Algo que claramente fue un error de diseño y que prácticamente todos los otros lenguajes para la JVM (y algunos de otras plataformas como .NET) dejaron atrás.

Si, los agregaron en la v1.2

Si, los agregaron en la v1.2 de java cuando se aceptaron también las clases anónimas y sí, son raros en ciertos tipos de código, en swing por ejemplo eran muy comunes, por ejemplo crear una ventana con un text field, un botón y un action listener se vería así:

JFrame frame = new JFrame() {{
   add( new JPanel() {{
       add( new JTextField() );
       add( new JButton("search") {{
            addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                   doSearch();
                }
            });
    }};
}};
frame.pack();
frame.setVisible(true);

Que es mucho más breve que el completo sin usar clases anónimas.

Imagen de Cid

Alguna vez los vi algo parecido

Digo que alguna vez vi algo parecido pero en este caso no son inicializadores,

                int opc = 1;
                switch(opc){
                    case 1:{
                        String msg = "Hola";
                        break;
                    }
                    case 2:{
                        String msg = "Adios";
                        break;
                    }                        
                }

El siguieente fragmento de código originaría error:

                int opc = 1;
                switch(opc){
                    case 1:
                        String msg = "Hola";                    
                        break;
                    case 2:
                        String msg = "Adios";                                            
                        break;
                }

Aqui las llaves de bloque de código sirven para definir un espacio "local" para un mismo nombre de variable y no cause error, pero no tienen que ver con los inicializadores porque esos pertenecen a la instancia y en este ejemplo pertenecen a un ambito local a la estructura case del switch.

Imagen de ezamudio

bloques

Esos son simples bloques de código dentro de un método. Sirven precisamente para lo que dices: creas un nuevo scope donde puedes declarar cosas nuevas y no serán visibles fuera de ese bloque.

style="display:inline-block;width:728px;height:90px"
data-ad-client="ca-pub-5164839828746352"
data-ad-slot="7563230308">