jueves, 20 de septiembre de 2012

[JAVA] Constructor & overridable methods

El otro día nos topamos en un curso de Java con uno de esos errores que, si no te das cuenta o no estás muy fino ese día, puedes tirarte bastante tiempo para solucionarlo. Como tardamos bastante en encontrar algún ejemplo o explicación buena de por qué ocurría eso, voy a explicarlo aquí. Se trata de un warning que se muestra en NetBeans pero que Eclipse, por alguna extraña razón, omite.
Warning: Overridable method call in constructor.

Indica que en un contructor no deberíamos llamar a métodos que se puedan sobrescribir. Es decir, solo deberíamos hacer llamadas a métodos static, private o final. Desde que estoy programando en Java es lo que me habían dicho. De hecho, creo que en C++ también decían que las funciones de los constructores debían ser privadas. Si lo pensamos bien, las funciones que se llaman en los constructores suelen ser de configuración, por lo que es lógico que sean privadas.



¿Pero qué pasa si llamamos a una clase sobrescribible dentro de un constructor?

Vamos a tener tres clases en este ejemplo: la clase Principal, que tendrá el main y solo sirve para llamar a las demás; la clase Persona, y la clase Nino, que heredará de persona (Es Niño, pero como sabéis, la ñ no se lleva muy bien con la informática).

Cada vez que creamos a una persona o un niño queremos contabilizarlo. Para ello usamos dos variables estáticas de la clase Principal, y llamamos a la función "contabilizar" en los constructores de ambas clases para que se contabilicen solos. Como veis, tampoco me he comido mucho el tarro :)

Esta es la clase Persona, la que tiene el error. La función contabilizar no debería ser default.
package jevyanj;

public class Persona {
 
 public Persona(){
  contabilizar();
 }
  
 void contabilizar(){
  Principal.personas += 1;
  System.out.println("Se ha añadido una persona.");
 }
}


Aquí está la clase Nino, que hereda de Persona. Y como quiere contabilizarse también, sobreescribe el método contabilizar con su propio código.
package jevyanj;

public class Nino extends Persona{

 public Nino(){
  contabilizar();
 }
 
 void contabilizar(){
  Principal.ninos += 1;
  System.out.println("Se ha añadido un niño");
 }
}


Y aquí la clase Principal, que crea una persona y un niño y muestra el recuento.
package jevyanj;

public class Principal {

 public static int personas;
 public static int ninos;

 public static void main(String[] args) {
  personas = 0;
  ninos = 0;
  
  Persona p = new Persona();
  Nino n = new Nino();
  
  System.out.println("\n Recuento:");
  System.out.println("\tPersonas: " + personas + "\n\tNiños: " + ninos);
 }
}


Bien, la ejecución de este código da como resultado esto:

Se ha añadido una persona.
Se ha añadido un niño
Se ha añadido un niño
 Recuento:
Personas: 1
Niños: 2


¿Hay una sola persona y dos niños? Si hemos creado una persona y un niño, debería ser al revés, puesto que un niño es una persona.

¿Qué está fallando?
La razón es que el método "contabilizar" no es privado, por lo que la clase Niño (o cualquier otra clase que herede de Persona) puede sobrescribirlo. El constructor de la clase Niño llama al de la clase Persona, por definición de herencia, y éste llama a la función contabilizar, pero está sobrescrita. Así que ejecuta el código "contabilizar" de Niño dos veces cuando se crea un nño, una por Persona y otra en el propio constructor de Niño, en vez de una vez para cada clase.

En resumen, cuando llamas al constructor de la clase que hereda y éste llama al constructor de la clase heredara, se ejecuta código de la clase que hereda en ambos casos.

¿Solución?
Hacer el método "contabilizar" sea privado, estático o final. En definitiva, no sobrescribible. De esta forma, aunque se llamaran igual los métodos, cada clase tendría el suyo propio sin sobrescribir nada. Y el resultado de ejecutar el programa sería el correcto:
Se ha añadido una persona.Se ha añadido una persona.Se ha añadido un niño Recuento: Personas: 2 Niños: 1

La conclusión podría ser: todo lo que puedas poner privado, déjalo privado ;)


PD: dudas, errores y consejos en los comentarios.
 
Licencia de Creative Commons
Pensamientos anónimos by JevyanJ is licensed under a Creative Commons Reconocimiento-CompartirIgual 3.0 Unported License.
Creado a partir de la obra en zyxdiario.blogspot.com.
Permissions beyond the scope of this license may be available at http://zyxdiario.blogspot.com.