En Java, como en otros lenguajes de programación orientados a objetos, las clases pueden derivar desde otras clases. La clase derivada (la clase que proviene de otra clase) se llama subclase. La clase de la que está derivada se denomina superclase. De hecho, en Java, todas las clases deben derivar de alguna clase. Lo que nos lleva a la cuestión ¿Dónde empieza todo esto? La clase más alta, la clase de la que todas las demás descienden, es la clase Object, definida en java.lang. Object es la raíz de la herencia de todas las clases. Las subclases heredan el estado y el comportamiento en forma de las variables y los métodos de su superclase. La subclase puede utilizar los ítems heredados de su superclase tal y como son, o puede modificarlos o sobre escribirlos. Por eso, según se va bajando por el árbol de la herencia, las clases se convierten en más y más especializadas.
Una subclase es una clase que desciende de otra clase. Una subclase hereda el estado y el comportamiento de todos sus ancestros. El término superclase se refiere a la clase que es el ancestro más directo, así como a todas las clases ascendentes.
Existen dos tipos de herencia sencilla y múltiple. Sencilla significa que sólo heredamos de una clase base, mientras que múltiple indica que tenemos varias clases base (por ejemplo un hidroavión hereda de barco y de avión). Java sólo soporta herencia simple.
Al utilizar la herencia aparecen dos conceptos: super y this, this representa al objeto completo, en cambio super, sólo representa la parte heredada de la clase base.
Cuando se hereda nos encontramos frente a un pequeño problema: ¿Qué sucede cuando se hereda un método de la clase base, el cual estamos redefiniendo en la clase derivada? Esto es un caso de sobrescritura de métodos. La solución es simple, cuando estemos ejecutando el método de un objeto derivado se llamará al método de su propia clase, es decir el redefinido. Si lo que se quiere es emplear el método de la clase base, hay que emplear una técnica que consiste en usar: super.método().
Los constructores no son heredados, pero sí llamados. Es decir, cuando se construye un objeto de la clase derivada se llama al constructor de la clase derivada, pero antes de comenzar a ejecutarse se llama al constructor de la clase base, que tras ejecutarse continua la ejecución del constructor de la clase derivada.
Se puede elegir qué constructor de la clase base es llamado, generalmente llamando al método super (), que representa al constructor de la clase base, pero al pasar parámetros distintos, seleccionamos qué constructor de la clase base queremos llamar.
Supongamos ahora que tenemos algo que es capaz de encenderse, de apagarse, de iniciar una reproducción, de parar una reproducción, sin duda todos pensamos en un reproductor, pero por esa descripción encajan objetos como reproductor de casete, reproductor de CD, el vídeo,...
Llamaremos a la descripción interfaz, y los objetos que cumplen ese interfaz (es decir, tienen todas las funciones que definen el interfaz) diremos que implementan el interfaz. Pensemos ahora en el hecho que una persona que sabe iniciar una reproducción de un CD también sabe iniciar una reproducción de vídeo, en ambos casos debe de encender el objeto, iniciar la reproducción, parar la reproducción y apagar el reproductor. Eso significa que para la persona es transparente el tipo (clase) real del objeto reproductor que posea, ya que la persona sabe que puede ponerlo en marcha, apagarlo, sin necesidad de conocer la clase real, tan sólo debe de saber que es un objeto del tipo reproductor.
Java permite el empleo de la herencia, característica muy potente que permite definir una clase tomando como base a otra clase ya existente. Esto es una de las bases de la reutilización de código, en lugar de copiar y pegar. En java, la herencia se especifica agregando la cláusula extends después del nombre de la clase. En la cláusula extends indicaremos el nombre de la clase base de la cuál queremos heredar.
Al heredar de una clase base, heredaremos tanto los atributos como los métodos, mientras que los constructores son utilizados, pero no heredados.
Construyamos la clase Taxista.java con el siguiente código:
public class Taxista extends Persona {
private int nLicencia;
public void setNLicencia(int num)
{
nLicencia = num;
}
public int getLicencia()
{
return nLicencia;
}
}
construyamos ArranqueTaxista.java:
public class ArranqueTaxista {
public static void main (String arg[]){
Taxista tax1 = new Taxista();
tax1.setNombre("Luis");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
}
}
Ahora intentemos usar el constructor que existía en la clase Persona que recibia el nombre de la persona y vamos a usarlo para la clase Taxista. Para ello construyamos la clase ArranqueTaxista2.java:
public class ArranqueTaxista2 {
public static void main (String arg[]){
Taxista tax1 = new Taxista("Jose");
tax1.setEdad(50);
System.out.println( tax1.getNombre());
System.out.println(tax1.getEdad());
System.out.println(tax1.getNLicencia());
}
}
Se genera un error de compilación, debido a que los constructores no se heredan, sino que hay que definir nuestros propios constructores. Agreguemos en la clase Taxista los siguientes constructores:
public Taxista(int licencia)
{
super();
nLicencia = licencia;
}
public Taxista(String nombre,int licencia)
{
super(nombre);
nLicencia = licencia;
}
Ahora si podremos compilar y ejecutar la clase ArranqueTaxista2. La llamada al método super indica que estamos llamando a un constructor de la clase base (pensemos que un Taxista antes que Taxista es Persona y por tanto tiene sentido llamar al constructor de Persona antes que al de Taxista). Además gracias al número de parámetros de la llamada a super podemos especificar cuál de los constructores de la clase base queremos llamar. En java se pueden emplear dos palabras clave: this y super.
Como vimos en la introducción a la programación orientada a objetos, this hace alusión a todo el objeto y super hace alusión a la parte heredada, por ello empleamos super para referenciar al constructor de la clase base.
Ahora vamos a agregar la función getNombre dentro de la clase Taxista, es decir, tenemos la misma función en Persona y en Taxista:
public String getNombre()
{
return "Soy un taxista y me llamo: " + super.getNombre();
}
Compilamos Taxista y ejecutamos ArranqueTaxista2. Veremos que el mensaje que aparece en pantalla demuestra que la función getNombre llamada es la de del tipo real del objeto construido, en este caso la de la clase derivada que es Taxista.
Tambien apreciamos que para acceder al atributo nombre es necesario acceder al método getNombre de la clase base (y por ello emplear super).
En java los atributos y métodos de la clase base pueden cambiar su modificador de visibilidad dentro de la clase derivada, la siguiente tabla recoge dichos cambios:
Modificadores en la clase base
public
private
protected
paquete
En la clase derivada se transforman en
public
inaccesible
protected
paquete
Inaccesible significa que, a pesar de haber sido heredado, no hay permisos en la clase derivada para poder acceder a dicho elemento inaccesible, pero aún así, se pueden llamar a métodos de la clase base que si pueden acceder y modificar al elemento.
Recordemos que protected significa que es private, pero que al heredar no se hace inaccesible, es decir que desde la clase derivada se puede acceder.