sábado, 31 de agosto de 2013

Java: creando objetos - Parte 1

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:

  1. Preinicialización de todos los atributos de la clase y todas sus superclases a sus valores por defecto.
  2. Evaluación de los argumentos, si los hay, que se vayan a pasar al otro constructor.
  3. Invocación al otro constructor (hasta llegar a Object): un constructor de la clase base u otro de la propia clase.
  4. Asignaciones a atributos y bloques de inicialización en estricto orden de aparición (Left - Right).
  5. Resto de instrucciones del cuerpo del constructor.
Esos pasos se ejecutan recursivamente hasta llegar a la clase Object, clase cuyo único constructor no tiene otro constructor al que invocar.

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:


(Actualizado 02/09/2013)

No hay comentarios:

Publicar un comentario