SOBREESCRITURA Y SOBRECARGA
1. Sobreescritura de métodos.
La sobreescritura de métodos consiste en la habilidad de definir cierto tipo de comportamiento que es específico para una subclase. Por ejemplo:
class Humano{
public void caminar(){
//caminar como un ser humano
}
//otros métodos de humanos...
}
class Pepe extends Humano{
public void caminar(){
//caminar de forma particular como Pepe
}
}
En la implementación anterior la clase Humano define un grupo de métodos que aplicará a todas sus subclases. Pepe por ejemplo, hereda todos los métodos heredables de Humano, pero por alguna razón, define un comportamiento específico para caminar. Quizá por que Pepe sea cojo, tenga un pie más largo que otro o por que camina muy rápido, en fin, de esto se trata la sobreescritura de métodos: las subclases pueden aprovechar todos los métodos que heredan de sus padres y además personalizar el comportamiento que no se acomode a sus necesidades.
No se debe confundir la sobreescritura de métodos con la implementación. Cuando se heredan métodos abstractos de una clase (abstracta por supuesto), estos se implementan no se sobreescriben.
Las reglas para sobreescribir métodos son:
- La lista de argumentos debe ser exactamente igual a la del método sobreescrito. Si esta regla no se cumple, probablemente se obtiene un método sobrecargado.
- El tipo de retorno debe ser el mismo o un subtipo del tipo de retorno declarado en el método sobreescrito en la superclase. Lo de subtipo es nuevo en JSE5, se le llama "Covariant Returns" y se verá más adelante.
- El nivel de acceso no puede ser más restrictivo que el del método sobreescrito.
- Los métodos marcados como private, final o static no pueden ser sobreescritos.
- El método que sobreescribe no puede lanzar 'Checked Exceptions' nuevas o de nivel superior a las declaradas en el método sobreescrito.
- El método que sobreescribe no tiene que declarar excepciones que no vaya a lanzar, no importa las que hayan declaradas en el método original.
OJO: Si un método no puede ser heredado tampoco puede ser sobreescrito.
Invocar la versión de la superclase de un método sobreescrito
Sobreescribir un método no quiere decir que el método de la clase de nivel superior ya nunca más puede ser invocado con una instancia de la subclase; la palabra reservada super hace referencia a la instancia de la superclase (sí, al crear la instancia de una subclase, también se crea una de la superclase) y con esta palabra podemos invocar los métodos que han sido sobreescritos en la subclase. Por ejemplo:
class Humano{
public void caminar(){
//caminar como un ser humano
}
}
class Pepe extends Humano{
public void caminar(){
super.caminar(); //invoco la forma de caminar de la clase superior
//implemento las particularidades del caminado de Pepe
}
}
El uso de la palabra super para invocar métodos sobreescritos, está restringido para los métodos de instancia que sean accesibles por la subclase (los no private).
2. Sobrecarga de métodos
- La sobrecarga de métodos permite reutilizar el mismo nombre de un método en una clase pero con una lista de argumentos diferente y se puede cambiar el tipo de retorno. Los métodos se pueden sobrecargar en la misma clase o una subclase puede sobrecargar uno o varios métodos de la clase padre. Un ejemplo:
class Humano{
public void caminar(int kilometros){
//caminar los metros indicados
}
public void caminar(Recorrido rr){
//seguir el recorrido pasado como parámetro
}
}
class Pepe{
public void caminar(Direccion dir){
//caminar hacia la dirección indicada
}
}
Note que en el ejemplo anterior el método caminar() de la clase Pepe no sobreescribe ninguno de Humano sino que lo sobrecarga. Así una instancia de la clase Humano tiene acceso a dos formas del método caminar: una que recibe el número de kilómetros y otra que recibe el recorrido a hacer; mientras una instancia de la clase Pepe tiene acceso a tres formas del método caminar: dos heredadas de Humano y otra sobrecargada en Pepe que recibe una dirección.
Las reglas para sobrecargar métodos son:
- Los métodos sobrecargados tienen que cambiar la lista de argumentos.
- Un método sobrecargado puede cambiar el tipo de retorno.
- Un método sobrecargado puede cambiar el modificador de acceso.
- Un método sobrecargado puede declarar excepciones nuevas o de nivel superior.
Dado el método public Integer calcularPrecioProducto(int valorCompra, double ganancia) throws Exception, algunas versiones sobrecargadas de este método pueden ser:
protected Double calcularPrecioProducto(int valorCompra, double ganancia)
public Integer calcularPrecioProducto(int valorCompra)
public Integer calcularPrecioProducto(double ganancia) throws SQLException
- La decisión de la versión del método que se va a invocar está basada en la lista de argumentos y se hace en tiempo de compilación. Por ejemplo al compilar y ejecutar la clase EjemploSobrecarga:
class Humano{}
class Pepe extends Humano{}
class EjemploSobrecarga{
void hacerAlgo(Humano hum){
System.out.println("Procesando un Humano...");
}
void hacerAlgo(Pepe pe){
System.out.println("Procesando Pepe...");
}
public static void main(String[] args) {
Humano pepe = new Pepe();
new EjemploSobrecarga().hacerAlgo(pepe);
}
}
La salida será: "Procesando un Humano...".
Aunque la instancia que se pasa como parámetro al método 'hacerAlgo()' es de la clase Pepe, el compilador solo sabe que la variable es una referencia a la clase Humano y por tanto decide invocar la versión que recibe tiene esta clase como argumento. La salida sería diferente si la variable se creará así: 'Pepe pepe = new Pepe();', en este caso el compilador reconoce una referencia a Pepe e invoca el método correspondiente.
Algo que nunca me ha quedado muy claro es que sucede cuando se pasa null como argumento, no se cuál es el criterio para elegir el método. En el ejemplo anterior que sucedería con la línea: 'new EjemploSobrecarga().hacerAlgo(null);'
El Final
Es muy importante reconocer si un método se está sobreescribiendo, sobrecargando o ambos. Hay que reconocer cuando hay errores de compilación o cuando una mala sobreescritura termina en una sobrecarga; esto se debe complementar muy bien con los conceptos de polimorfismo para que dado cualquier código, con la complejidad que sea, podamos predecir la salida.
En SProgramando he publicado un listado de preguntas sobre el tema.