====== Herencia de Clases ======
En Java podemos tener clases que heredan de otras (extends, o extienden). **Cuando una clase hereda de otra puede hacer uso de los miembros de su clase padre.** Además, permite sobrescribir métodos en caso de que en una subclase queramos concretar su funcionamiento.
Debemos tener en cuenta que toda clase en Java hereda de la clase ''Object'' de forma directa o indirecta, y que por tanto hay una serie de métodos que heredan todas las clases.
{{ :bloque3:herencia-clases.png?300|}}
==== ¿Para qué sirve? ====
La herencia es un mecanismo mediante el cual aprovechamos la implementación de operaciones de una clase padre (reutilización de código) y creamos nuevas clases hijas a partir de esa clase:
* Heredando y posiblemente modificando y/o añadiendo operaciones.
* Heredando y posiblemente añadiendo atributos.
**Debemos tener en cuenta que un método o atributo heredado mediante el mecanismo de herencia no puede ser suprimido en la subclase.**
Podríamos decir que la herencia nos permite usar el código creado en una clase para no tener que volverlo a escribir en una subclase. Todas las subclases tienen las operaciones de la superclase, y así no tenemos que repetir el código en cada una.
Otro planteamiento seria, si me encuentro con la necesidad de tener dos clases cuyas operaciones (métodos) son iguales, pero que cada una tiene otras operaciones propias diferentes, debo concluir que esas dos clases tienen un parte en común, la cual debería definirse en una superclase, permitiendo a estas subclases implementar solo las operaciones específicas de cada una.
===== Declaración de una Subclase =====
Si entendemos que una clase son “los planos” que definen la creación de un ejemplar (objeto, instancia) de esa clase, podemos entender que cuando una clase hereda de otra, lo que está haciendo es usar los planos de la clase padre, y añadirle más información para crear más o diferentes funcionalidades de las que tenía.
Podemos llamar a las clases hijas ''subclases'' y a la clase padre, ''superclase''.
Para que una subclase herede de una superclase debe usarse ''extends'' en la definición de la subclase.
Ejemplo de superclase: ''Vehiculo''. Hay muchos tipos de vehículos (avión, moto agua, coche, moto). Es una clase estándar como cualquier otra.
public class Vehiculo{
private int km;
private String matricula;
public int getKm(){
return km;
}
public void setKm(int km){
this.km = km;
}
public String getMatricula(){
return matricula;
}
public void setMatricula(String matricula){
this.matricula = matricula;
}
}
{{:bloque3:vehiculoherencia.png?400 |}}
Ahora vemos su uso y vemos que aparecen los métodos ''getKm()'', ''setKm()'', ''getMatricula()'' y ''setMatricula()''.
Además del resto de métodos heredados de la clase ''Object''.
----
==== Palabra clave Extends ====
Vamos a crear ahora la clase ''Coche'' que //extiende// las funcionalidades de la clase ''Vehiculo'':
public class Coche extends Vehiculo{
//Campo específico de Coche
private int cilindrada;
//Metodos específicos de Coche
public void setCilindrada(int cilindrada){
this.cilindrada = cilindrada;
}
public int getCilindrada(){
return cilindrada;
}
}
{{:bloque3:cocheherencia.png?400 |}}
Aquí vemos como aparecen los métodos heredados de la superclase Vehiculo: ''getKm()'', ''setKm()'', ''getMatricula()'' y ''setMatricula()''.
Además tiene sus propios miembros específicos ''setCilindrada()'' y ''getCilindrada()''.
También están los miembros heredados de la
clase ''Object'', comunes a todas las clases.
Ambas clases (Vehiculo y Coche) tienen el constructor estándar sin parámetros.
----
Una subclase solo puede acceder a los miembros de la superclase que sean ''public'' o ''protectec''.
==== Impedir la herencia ====
Si queremos que una clase no permita extender su funcionalidad, o lo que es lo mismo, no permita ser heredada, definiremos dicha clase como ''final''.
public final class Vehiculo{
...
}
La palabra clave ''final'' la hemos usado en otras ocasiones para crear variables que no se puedan modificar (constantes), y también se aplica a las clases y a los métodos con dicha finalidad.
Por ejemplo las clases ''String'' o ''Math'' no se pueden heredar, ya que están definidas como ''final''.
===== Constructor de la subclase =====
**Los constructores no se heredan**. __Siempre que se crea una instancia de una subclase, se crea primero una instancia de la superclase__. Esta instanciación puede ser implícita, o explícita, o lo que es lo mismo, invisible a nivel de código, o visible.
En una clase se puede definir un constructor de forma explícita, o no definir ninguno. **Cuando no definimos un constructor, Java crea implícitamente uno**. No tiene parámetros, y no hace nada, pero sirve para crear instancias de la clase. Se conoce como constructor **no-args**, sin argumentos (parámetros).
Atendiendo a lo anterior, con los constructores de la subclase puede ocurrir 2 cosas:
* **La superclase tiene un constructor sin parámetros** (ya sea el que define Java de forma implícita cuando no hemos definido ninguno, o uno definido por nosotros que no tiene parámetros). En este caso en la subclase podemos definir un constructor, o no definir ninguno (Java crea un constructor **no-args** implícitamente). Cada vez que creemos una instancia de la subclase, Java llamará automáticamente al constructor de la superclase de forma implícita, invisible.
* **La superclase no tiene un constructor sin parámetros** (o sea, se ha definido uno con parámetros). En este caso Java no puede llamar de forma implícita al constructor de la superclase, ya que necesita saber qué valor dar a sus parámetros. Entonces estamos obligados a crear también un constructor en la subclase, cuya primera instrucción es una llamada al constructor de la superclase. Esto se hace usando la instrucción ''super()''.
==== Palabra clave super ====
Si en cualquier clase tenemos la palabra ''this'' que representa una referencia al objeto actual de la propia clase, en una subclase tenemos además la palabra ''super'' que contiene una referencia al objeto actual de la superclase.
Esto tiene su explicación en que, para crear una instancia de una subclase, obligatoriamente se debe crear primero una instancia de la superclase (explícita o implícitamente).
A la hora de crear un constructor para la subclase, es necesario llamar al constructor de la superclase. Cuando el constructor/es de la superclase recibe parámetros, no nos queda más remedio que llamarlo nosotros:
public class Vehiculo{
...
//Constructor de la superclase
public Vehiculo(int km, String matricula) {
this.km = km;
this.matricula = matricula;
}
}
........
public class Coche extends Vehiculo{
...
//Constructor de la subclase
public Coche(int km, String matricula) {
super(km, matricula);
}
//Ó podemos tener también otro constructor en la subclase
public Coche(int km, String matricula, int cilindrada) {
super(km, matricula);
this.cilindrada = cilindrada; //Atributo específico de la subclase.
}
}
Como vemos, si una clase hereda de otra, **y la superclase solo tiene constructores con argumentos, la subclase debe llamar a dicho constructor con al menos el mismo número de argumentos**, aunque puede usar más.
Una vez vez que se crea el constructor de la superclase, podemos crear los constructores de la subclase.
**Si una superclase no tiene contructor no-args, la subclase debe tener al menos los mismos constructores, ya que estos no se heredan.**
La palabra ''super'' también me permite acceder a los miembros de una superclase -> ''super.arrancar()''
===== Sobrescritura de métodos: Override =====
Cada vez que una clase hereda un método, se tiene la posibilidad de sobrescribir el método.
La sobrescritura de métodos se usa para definir el comportamiento específico de un método que tiene un comportamiento demasiado general en la superclase. Esto se denomina ''Override''.
//Superclase Vehiculo
public class Vehiculo{
...
public void arrancar(){
System.out.println("Vehiculo arrancado...");
}
}
//Subclase Coche
public class Coche extends Vehiculo{
...
@Override
public void arrancar(){
//modificamos el comportamiento del método
System.out.println("Coche arrancado...");
}
}
//Clase con método main
...
public static void main(String[] args){
Vehiculo miVehiculo = new Vehiculo();
Coche miCoche = new Coche();
miVehiculo.arrancar(); //Muestra: "Vehiculo arrancado..."
miCoche.arrancar(); //Muestra: "Coche arrancado..."
}
**Cuando se llama a un método, este se busca primero en su propia clase, y si no está, se buscará en la superclase, y así sucesivamente hasta llegar a la clase Object**. Si no está en ninguna clase, el compilador nos lo indica como un error.
====Etiqueta @Override====
Cuando sobrescribimos un método es aconsejable (aunque no obligatorio) indicarle sobre su declaración, la etiqueta ''@Override'':
@Override
public String toString(){
return "Coche- matricula : " + matricula + " cilindrada: " + cilindrada;
}
**Existen una serie de normas que se deben cumplir al sobrescribir un método**, y la etiqueta ''@Override'' nos ayuda a cumplirlas:
* Los parámetros del método sobrescrito deben ser exactamente iguales que los del original (tipo y cantidad). Si no, estaríamos sobrecargándolo (2 métodos distintos con el mismo nombre).
* El tipo de retorno debe ser el mismo o un subtipo del tipo de retorno del método de la superclase.
* El modificador de visibilidad **no puede ser más restrictivo** que el original, aunque si menos restrictivo.
* No se puede sobrescribir un método marcado como ''final'' o ''static''.
__Si incumplimos alguna de estas reglas__ y estamos usando el tag ''@Override'', __el compilador nos lo indicará__.
==== Impedir el Override de un método ====
Si quiero que un método no pueda ser sobrescrito (redefinido) en una subclase, debo indicarle en su definición el modificador ''final'':
public class Vehiculo{
public final void arrancar(){
System.out.println("Vehiculo arrancado...");
}
}
Ahora ninguna subclase de Vehiculo puede sobrescribir el método: **está obligada a usar el método original de Vehículo sin poder modificarlo**.
===== Modificador Protected =====
Mediante el mecanismo de herencia, una subclase solo puede acceder a los __miembros de su superclase__ que tengan visibilidad ''public'' o ''protected''.
El modificador de visibilidad ''protected'' actúa igual que el modificador ''private'', pero permite que una subclase puede acceder a los miembros marcados como ''protected'', incluso aunque estén en paquetes diferentes.
**Un miembro protected permite ser accedido por la misma clase o por sus subclases, mientras que private solo permite por la propia clase**.
public class Vehiculo{
private int kilometros; //Ninguna clase más tiene acceso a él
protected int matricula; //La subclase tiene acceso
protected void arrancar(){
System.out.println("Vehiculo arrancado...");
}
}
public class Coche extends Vehiculo{
public void mostrarDatos(){
//puedo acceder al atributo matricula
System.out.println("La matricula del coche es: " + matricula);
//No puedo acceder al atributo kilometros
System.out.println("Los km del coche son: " + kilometros); //Error de compilación
}
//Sobrescribo el método arrancar y lo hago público
@Override
public void arrancar(){
System.out.println("Coche arrancado...");
}
//Clase con método main
public static void main(String[] args){
Vehiculo miVehiculo = new Vehiculo();
Coche miCoche = new Coche();
miVehiculo.arrancar(); //Error de compilación: no tengo acceso
miCoche.arrancar(); //Muestra: "Coche arrancado...");
miCoche.mostrarDatos(); //Muestra la matrícula
}
----
(c) {{date> %Y}} Fernando Valdeón