¿Sabes cuál es el orden de inicialización en Java? En el examen de certificación OCAJ8P es muy posible que te encuentres con alguna pregunta relativa a esta cuestión. Parece sencillo, pero puede complicarse bastante.
More...
¿Qué es el orden de inicialización en Java?
Por orden de inicialización en Java se entiende el orden en el que se inicializan variables, referencias y otros bloques de código, así como en qué momento aquellas adquieren el valor que se les asigne.
Parece sencillo, ¿no? No obstante, ya sabes que en el examen de certificación van a intentar complicarte las cosas de tal manera, que no va a parecerte tan sencillo. Mira este ejemplo:
¿Cuál crees que es el resultado? Pues aunque parezca extraño, el resultado es 0, y no 4. Tranquil@; para cuando termines de leer este artículo tendrás muy claro el porqué.
Orden de inicialización en Java: herencia
Esta parte es sencilla: cuando instancias una clase, todas las superclases de esa clase se instancian en cascada. En el ejemplo anterior, como Beta extiende Alfa, la clase Alfa se instancia en primer lugar. Mira el siguiente ejemplo:
En este caso, el resultado es Alfa Beta Gamma.
Al instanciar una clase en #Java, todas sus superclases se instancian en cascada #javautodidacta
Esta es la razón por la que el resultado del programa es 0 y no 4: al crear un objeto de tipo Beta, la superclase Alfa se instancia en primer lugar. Dentro del constructor de Alfa hay una llamada al método method(), que ha sobreescrito la clase Beta (tengo otro artículo sobre la herencia en Java; échale un vistazo). Así, se ejecuta el método de la clase Beta antes de que esta clase se instancie. Como consecuencia de esto, number toma su valor predeterminado, que es cero, ya que aún no se ha inicializado.
Pero, ¿y si number fuera una variable estática?
Elementos estáticos en el orden de inicialización en Java
En este caso, se imprime el valor 4. ¿Por qué, si la clase Beta sigue sin instanciarse?
La respuesta es muy sencilla: las variables y referencias y los inicializadores de instancia solo se inicializan cuando se instancia la clase. Por otro lado, las variables y referencias y los inicializadores estáticos se inicializan aunque se acceda a la clase sin realizar llamadas al constructor. Es decir, para inicializar estos últimos no es necesario que la clase se instancie, ya que al ser estáticos no dependen de una instancia de la clase.
Quizá esto último te llame la atención: en el código sí hemos hecho una llamada al constructor de Beta. Sí, es cierto, pero este constructor todavía no se había ejecutado; el constructor Alfa instanció primero su clase y, acto seguido, ejecutó el método de la clase Beta antes de que el hilo de ejecución instanciara el objeto de tipo Beta.
Por si no lo sabes, un inicializador de instancia es un bloque de código entre llaves; si va precedido de la palabra clave static, se le llama inicializador estático.
Orden de inicialización en #Java: los campos estáticos se inicializan cuando se accede a la clase; los campos de instancia se inicializan cuando se instancia la clase.
Estáticos vs. de instancia
Otra diferencia que existe entre los campos estáticos y los de instancia es que los estáticos, al estar vinculados a la clase, solo se ejecutan una vez; los de instancia, cada vez que se cree una instancia de la clase. No obstante, cuando tenemos un sistema de herencia, las cosas se vuelven a complicar:
¿Cuál es el resultado de ejecutar este código? Según lo que te he dicho más arriba, podrías pensar en lo siguiente:
ALFA static
ALFA no static
BETA static
BETA no static
0
ALFA no static
0
BETA no static
En primer lugar, los campos estáticos; a continuación, los no estáticos. Es así, pero el resultado no es exactamente este. El resultado real de ejecutar el ejemplo anterior es el siguiente:
ALFA static
BETA static
ALFA no static
0
BETA no static
ALFA no static
0
BETA no static
Esto se debe a que, en el orden de inicialización en Java, las superclases inician, en primer lugar, todos sus campos estáticos en cascada. Si introducimos una subclase más:
Obtenemos el siguiente resultado:
ALFA static
BETA static
GAMMA static
ALFA no static
0
BETA no static
GAMMA no static
Los últimos dos puntos se repiten por cada subclase, en orden de herencia.
Orden de inicialización en Java, sin herencia
Una vez que ha quedado claro el orden de inicialización en Java con herencia de por medio, vamos a ver cuál es el orden de inicialización dentro de una misma clase.
¿Sabes por qué no compila el siguiente ejemplo?
Main.java:12: error: illegal forward reference
System.out.println("z = " + z);
^
1 error
¿Qué problema hay, si z está declarada correctamente?
El problema no reside en cómo está declarada, sino en dónde.
Como ya te he dicho antes, los campos estáticos se inicializan en primer lugar; a continuación, se inicializan los campos de instancia. No obstante, los campos de cada uno de estos dos tipos no se inicializan de manera simultánea, sino en el orden en el que aparecen en la clase.
Por ello, el inicializador de instancia se inicializa antes que la variable z. Es por esta razón por la que no puede acceder aún a ella. Ocurriría lo mismo si el inicializador y la variable fueran estáticos: se inicializan en el orden en el que aparecen.
Por regla general se suelen declarar las variables y referencias de instancia en primer lugar en la clase (y ahora sabes por qué), pero en el examen de certificación te puedes encontrar cualquier cosa (y, además, el código que te aparezca en el examen no tendrá colores distintos: harán todo lo posible para confundirte).
Llamadas al constructor desde un bloque estático
¿Y en este caso? Ya sabes que el constructor se ejecuta en último lugar, ¿pero y si instanciamos la clase con una llamada al constructor en el primer inicializador estático?
Uno (estático)
Uno (no estático)
Alfa instanciado
Dos (estático)
Uno (no estático)
Alfa instanciado
Como ves, en ese caso se detiene el orden predeterminado de inicialización en Java.
Al existir una llamada al constructor en el primer inicializador estático, se detiene la inicialización de campos estáticos y se pasa a inicializar los campos de instancia, ejecutando el constructor en último lugar. Una vez que se ha instanciado este objeto, se continua el orden de inicialización que se había interrumpido.
Marta ha aprendido el orden de inicialización en Java. Sé como Marta.
Ejercicios recomendados
Lo único que te puedo recomendar es que te crees una clase Java (en Ideone, o en el bloc de notas) y empieces a experimentar, para ver en qué otras circunstancias se puede modificar el orden de inicialización. Como te he comentado más arriba, es muy importante que tengas muy claro los detalles para el examen de certificación, incluidos casos que no vas a ver más en otras circunstancias ya que son ejemplos de malas prácticas.
¿Qué otros ejemplos se te ocurren en los que el orden de inicialización en Java no se lleva a cabo de la manera predeterminada? ¿Tienes alguna duda al respecto? ¡Espero tus comentarios!