Introducción
Los constructores en Java tienen ciertas características en las que no solemos reparar y que conviene tener en cuenta. No hacerlo, puede dar resultados inesperados e incluso llevar al programador a cometer un error que es muy difícil de encontrar (y posiblemente solucionar) del que espero hablar en un artículo venidero:
¿Has visto alguna vez el mensaje
"Constructor calls overridable method"?
Pero vayamos poco a poco. Entendamos primero como funcionan los constructores.
La especificación técnica de todo lo que explico aquí está en el siguiente enlace:
Para no espantar al lector con un artículo inmenso, ja ja ja, he decido separar este artículo en varios artículos pequeños, tratando de centrar cada uno de ellos en explicar una característica de la creación de objetos.
Algoritmo general de creación de objetos
Al crear un nuevo objeto, una vez que se ha reservado memoria suficiente para todos los atributos de la clase (y todos los atributos de sus superclases, incluidos los campos ocultados), se realizan los siguientes pasos:
- Preinicialización de todos los atributos de la clase y todas sus superclases a sus valores por defecto.
- Evaluación de los argumentos, si los hay, que se vayan a pasar al otro constructor.
- Invocación al otro constructor (hasta llegar a Object): un constructor de la clase base u otro de la propia clase.
- Asignaciones a atributos y bloques de inicialización en estricto orden de aparición (Left - Right).
- Resto de instrucciones del cuerpo del constructor.
Recursión de constructores
El programador invoca explícitamente uno de los constructores de la clase.
La primera instrucción de un constructor (al menos a la vista del programador, puesto que la preinicialización ya se ha ejecutado) es siempre una invocación a otro constructor:
- Un constructor de la clase base (salvo en el caso de Object, que no hereda de nadie). Muchas veces, esta sentencia la pone el compilador por nosotros, invocando implícitamente al constructor sin argumentos de la clase base (si existe, si no, se producirá un error de compilación).
- Otro constructor de la propia clase.
Lo primero que ocurre, es que si la invocación al otro constructor lleva argumentos, se evalúan dichos argumentos. En este punto, aún no se pueden usar referencias a this ni a super. Es decir, que el cálculo de los argumentos no puede depender del objeto en construcción.
Al retornar del otro constructor, se continúa ejecutando el cuerpo del constructor actual. En este punto, ya se pueden utilizar referencias a this y a super.
Ejemplo
El pequeño programa ConstructorRecursion sirve para ilustrar la recursión de constructores. Además, como comentarios, hay código alternativo que fallaría por no ser válido en ese punto. Puedes ver y copiar el código fuente en el siguiente enlace:
La salida del programa es la siguiente:
echoAndPrint: No arguments echoAndPrint: aValue= No arguments echoAndPrint: Root constructor echoAndPrint: Initial value: null echoAndPrint: aValue= No arguments A derived object: pruebas.ConstructorRecursion$Derived@a62fc3 variable<aValue= No arguments> constant=<Constant attribute.>
Se invoca la siguiente cadena de constructores:
Derived() Derived(String) Base(String) Root() Object()
Obsérvese como curiosidad que al inicializar el atributo variable uso su propio valor como parte de la inicialización. Esto es posible porque en ese punto ya se puede referenciar a this y super y además el atributo ha sido preinicializado a null.
Enlaces
Enlaces de interés relacionados con este artículo:
- Java Language Specification 12.5. Creation of New Class Instances
http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5
- Java Language Specification 4.12.5. Initial Values of Variables
http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.12.5
(Actualizado 02/09/2013)
No hay comentarios:
Publicar un comentario