domingo, 29 de septiembre de 2013

Java: creando objetos - Parte 2


Introducción


Siguiendo con la explicación de algunas características no triviales de la creación de objetos en Java, ahora me voy a centrar en una cuestión un tanto confusa y que probablemente nadie domina del todo:

¿dónde, cuándo y en qué orden se inicializan los atributos?

Preinicialización de atributos


Al construir un objeto nuevo, lo primero que ocurre es que se reserva memoria para todos sus atributos y para todos los atributos de todas sus superclases.

Una vez reservada la memoria, cada atributo se inicializa a su valor por defecto:
  • byte: cero, concretamente (byte)0.
  • short: cero, concretamente (short)0.
  • int: cero, concretamente 0.
  • long: cero, concretamente 0L.
  • float: cero, concretamente 0.0f.
  • double: cero, concretamente 0.0d.
  • char: el carácter null, o sea '\u0000'.
  • boolean: false.
  • all reference types: null.


Para recordarlo fácilmente, basta con pensar que el área de memoria se rellena a 0, generando false para boolean, el carácter null para char, 0 para todos los números y null para las referencias a objetos.


Orden de inicialización de atributos


Recuérdese que los atributos ya se han preinicializado automáticamente a sus valores por defecto.

Al crear un objeto, se pueden inicializar sus atributos explícitamente en 3 sitios diferentes:

  1. Asignaciones: al declarar el atributo, se le puede asignar un valor.
    ...
    private int att= 1;
    ...
    
  2. Bloques de inicialización: son bloques anónimos a nivel de clase (código encerrado entre llaves) que el compilador copia literalmente en todos los constructores que invoquen al constructor de la clase base, justo tras dicha invocación (es decir, que si se invoca a otro constructor de la propia clase no se replica el código en dicho punto, sino que sólo se hace tras las invocaciones a un constructor de la clase base).
    ...
    {
        this.att= 1;
    }
    ...
    
  3. Cuerpo de los constructores: dentro de un constructor se pueden asignar valores a los atributos.
    ...
    public MyClass() {
        this.att= 1;
    }
    ...
    

En los 3 casos, el cálculo de los valores de los atributos ya puede hacer uso de referencias a this y super.

Las clases anónimas no pueden tener constructores explícitos, pero sí los otros 2 métodos de inicialización.

El orden en el que se ejecutan las acciones es el siguiente:
  • Preinicialización.
  • Tras invocar al constructor de la clase base, Asignaciones y Bloques de inicialización en el orden en que aparecen en el código fuente.
  • Cuerpo del constructor.
Destacar que las Asignaciones y los Bloques de inicialización tienen la misma precedencia, no se agrupan las asignaciones por un lado y los bloques de inicialización por otro ni nada por el estilo, sino que tal cual van a apareciendo unos y otros en el código fuente se van ejecutando.


Ejemplo


El pequeño programa InitializationOrder sirve para ilustrar y comprobar el orden en el que se inicializan los atributos de una clase. Puedes ver y copiar el código fuente en el siguiente enlace:


Todos los atributos que se crean son de tipo String, creando todas las Strings en el método next(String), que prefija cada String con el número de invocaciones al método, de modo que, el propio valor de la String indica cuando fue creada.

El método echo(String) sólo sirve para realizar una traza del programa.

Observando los resultados del programa y cambiando de orden algunas de sus instrucciones se puede comprobar lo que se explica en este artículo.

Conclusiones


Dado que el orden de inicialización depende del orden de aparición en el código fuente, yo recomiendo encarecidamente tener mucho cuidado al combinar los diferentes tipos de inicializaciones, ya que, mover bloques de código con posteridad (tal vez por otra persona que mantenga el código) podría provocar un comportamiento diferente y sería un error bastante difícil de encontrar.

No existe una solución perfecta, al fin y al cabo, los diferentes métodos de inicialización son herramientas que pueden resultar útiles en determinadas circunstancias. Sin embargo, en general, la solución que resulta más legible (y por tanto más mantenible) consiste en inicializar los atributos en un único constructor, que reciba todos los argumentos que sean necearios. Por comodidad, se pueden definir otros constructores más sencillos que invoquen al complejo con los valores iniciales. Por ejemplo:



public class Point {

    public int x;
    public int y;

    public Point(int aX, int aY) {
        super();

        this.x= aX;
        this.y= aY;
    }

    public Point() {
        this(0, 0);
    }

}


Recalco que esto es tan sólo una recomendación personal basada en la experiencia.De todas formas, no siempre es posible hacer esto. Por ejemplo, a veces cada constructor de una clase invoca a un constructor distinto de la superclase. En tal caso, puede resultar útil crear un bloque de inicialización.

Enlaces


Enlaces de interés relacionados con este artículo:


(Actualizado 29/09/2013)

Source code: InitializationOrder

Código fuente del programa InitializationOrder

Ver la entrada correspondiente en el siguiente enlace:


Java: creando objetos - Parte 2



import java.io.PrintStream;

public class InitializationOrder {

    //static attribute for counting invocations.
    private static int nextValue= 0;
    //--------------------------------------------------------------------------

    //Prefixes the argument with the current value of nextValue and increases it.
    public static String next(String aString) {
        String str= String.format("%02d%s"
            , InitializationOrder.nextValue++
            , aString
        );

        return str;
    }
    //--------------------------------------------------------------------------

    //Helper method.
    public static String makeString(String[] aFormats) {
        StringBuilder buffer= new StringBuilder();
        for (int i= 0; i < aFormats.length; i++) {
            buffer.append( aFormats[i] );
        }

        String str= buffer.toString();
        str= String.format(str);

        return str;
    }
    //--------------------------------------------------------------------------

    public static String echo(String aMessage) {
        //This sentence is useful only for tracing purposes. It can be commented.
        System.out.format("echo: %s%n", aMessage);

        return aMessage;
    }
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------

    //Example.
    public static void main(String[] args) {
        final PrintStream out= System.out;
        Object aux;

        aux= new Super();
        out.format("Using Super implicit (automatic) constructor:%n%s%n", aux);
        out.format("..........%n");

        aux= new Base("FIFTH: Initialized at the end of Base(String).");
        out.format("Using Base(String):%n%s%n", aux);
        out.format("..........%n");

        aux= new Base();
        out.format("Using Base():%n%s%n", aux);
        out.format("..........%n");

        aux= new Base() {

            //Local anonymous class attributes.
            private String localFirst= echo(next("Local class instance attribute assignment."));
            private String localSecond;
            
            //Local anonymous Initialization block.
            {
                this.localSecond= echo(next("Local class initialization block."));
            }
            
            @Override
            public String toString() {
                String str= makeString(new String[]{
                    "\tlocalFirst=" + this.localFirst + "%n",
                    "\tlocalSecond=" + this.localSecond + "%n",
                });
                str= String.format("%s%s", super.toString(), str);

                return str;
            }
            //--------------------------------------------------------------------------

        };
        out.format("Using Anonymous Local Class extending Base():%n%s%n", aux);
        out.format("..........%n");

        out.format("%n");
    }
    //--------------------------------------------------------------------------


public static class Super {

    private String superFirst=  echo(next("FIRST: Super instance assignment."));
    private String superSecond= echo(next("SECOND: Super Instance assignment: this one is changed later."));
    
    //Initialization block 1.
    {
        this.superSecond= echo(next("SECOND: Super initialization block 1."));
    }
    //--------------------------------------------------------------------------

    //This class has no explicit constructor.

    @Override
    public String toString() {
        String str= makeString(new String[]{
            "\tsuperFirst=" + this.superFirst + "%n",
            "\tsuperSecond=" + this.superSecond + "%n",
        });
        str= String.format("%s: atributes:%n%s", this.getClass(), str);

        return str;
    }
    //--------------------------------------------------------------------------

}
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------


public static class Base extends Super {

    //First of all, all the attributes are initialized with their default value.
    //0 for int, false for boolean, null for object referencies, etc.
    private String basePreinitialized;//In this case, the default value is null.
    private String baseFirst=  echo(next("FIRST: Base instance assignment."));
    private String baseSecond;

    //Initialization block 1.
    {
        this.baseSecond= echo(next("SECOND: Base initialization block 1."));
        this.baseThird= echo(next("SECOND: Base initialization block 1. This one is changed later."));
    }

    //Initialization block 2.
    {
        this.baseThird= echo(next("THIRD: Base initialization block 2."));
    }

    //Initialization block 3.
    {
        this.baseFourth= echo(next("FOURTH: Base initialization block 3. This one is changed later."));
    }

    private String baseThird;
    private String baseFourth= echo(next("FOURTH: Base instance assignment."));

    private String baseFifth= echo(next("FIFTH: Base instance assignment. This one is changed later."));
    //--------------------------------------------------------------------------

    public Base(String aFifth) {
        super();
        
        this.baseFifth= aFifth;
        echo("End of Base(String)");
    }
    //--------------------------------------------------------------------------

    public Base() {
        this( next("Modified at the end of Base().") );

        this.baseFifth= next("FIFTH: " + this.baseFifth);
        echo("End of Base()");
    }
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------

    @Override
    public String toString() {
        String str= makeString(new String[]{
            "\tbasePreinitialized=" + this.basePreinitialized + "%n",
            "\tbaseFirst=" + this.baseFirst + "%n",
            "\tbaseSecond=" + this.baseSecond + "%n",
            "\tbaseThird=" + this.baseThird + "%n",
            "\tbaseFourth=" + this.baseFourth + "%n",
            "\tbaseFifth=" + this.baseFifth + "%n",
        });
        str= String.format("%s%s", super.toString(), str);

        return str;
    }
    //--------------------------------------------------------------------------

}
    //--------------------------------------------------------------------------
    //--------------------------------------------------------------------------

}//End of InitializationOrder.