Herencia, Clases Abstractas, Clases Finales, Casting, operador instanceof
La herencia es una de las características fundamentales de la
Programación Orientada a Ojetos.
Mediante
la herencia podemos definir una clase a partir de otra ya existente.
La clase nueva se llama clase derivada o subclase y la clase existente se llama clase
base o superclase.
En UML la herencia se representa con una flecha apuntando desde la clase
derivada a la clase base.
La clase
derivada hereda los componentes (atributos y métodos) de la clase base.
La finalidad de la herencia es:
-
Extender la funcionalidad de la clase
base: en la clase derivada se pueden añadir
atributos y métodos nuevos.
-
Especializar el comportamiento de la clase
base: en la clase derivada se pueden modificar
(sobrescribir, override) los métodos heredados para adaptarlos a sus
necesidades.
La herencia permite
la
reutilización del código, ya que
evita tener que reescribir de nuevo una clase existente cuando necesitamos
ampliarla en cualquier sentido. Todas las clases derivadas pueden utilizar el
código de la clase base sin tener que volver a definirlo en cada una de ellas.
Reutilización de código: El código se escribe una vez en la clase
base y se utiliza en todas las clases derivadas.
Una clase base puede serlo de tantas derivadas como se desee: Un solo
padre, varios hijos.
Herencia múltiple en Java: Java no soporta la herencia múltiple. Una clase derivada solo puede tener una
clase base.
Diagrama UML de herencia múltiple no permitida en Java
La herencia expresa una relación “ES UN/UNA” entre la clase derivada y la clase base.
Esto significa que un objeto de una clase derivada es también un
objeto de su clase base.
Al contrario NO es
cierto. Un
objeto de la clase base no es un objeto de la clase derivada.
Por ejemplo, supongamos una clase Vehiculo
como la clase base de una clase Coche. Podemos decir que un Coche es un
Vehiculo pero un Vehiculo no siempre es un Coche, puede ser una moto, un
camión, etc.
Un objeto de una clase derivada es a su vez
un objeto de su clase base, por lo tanto se puede utilizar en cualquier lugar
donde aparezca un objeto de la clase base. Si esto no fuese posible entonces la
herencia no está bien planteada.
Ejemplo de herencia
bien planteada:
A partir de una clase Persona que tiene como
atributos el nif y el nombre, podemos obtener una clase derivada Alumno. Un
Alumno es una Persona que tendrá como atributos nif, nombre y curso.
Ejemplo de herencia mal planteada:
Supongamos una clase Punto que tiene como atributos
coordenadaX y coordenadaY.
Se puede crear una clase Linea a partir de la clase
Punto. Simplificando mucho para este ejemplo, podemos considerar una línea como
un punto de origen y una longitud. En ese caso podemos crear la Clase Linea
como derivada de la clase Punto, pero el planteamiento no es correcto ya que no
se cumple la relación ES UN
Una Linea NO ES un Punto. En este caso no se debe
utilizar la herencia.
Una clase derivada a su vez puede ser clase base en un nuevo proceso de
derivación, formando de esta manera una Jerarquía de Clases.
Las
clases más generales se sitúan en lo más alto de la jerarquía. Cuánto más
arriba en la jerarquía, menor nivel de detalle.
Cada clase derivada debe implementar únicamente
lo que la distingue de su clase base.
En java todas las clases derivan directa o indirectamente de la clase
Object.
Object es la clase base de toda la jerarquía de clases Java.
Todos los objetos en un programa Java son Object.
CARACTERÍSTICAS DE LAS CLASES DERIVADAS
Una clase
derivada hereda de la clase base sus componentes
(atributos y métodos).
Los constructores no se heredan. Las
clases derivadas deberán implementar sus propios constructores.
Una clase derivada puede acceder a los miembros públicos y protegidos
de la clase base como si fuesen miembros propios.
Una clase
derivada no tiene acceso a los miembros privados de la clase base. Deberá
acceder a través de métodos heredados de la clase base.
Si se necesita
tener acceso directo a los miembros privados de la clase base se deben declarar
protected en lugar de private en la clase base.
Una clase
derivada puede añadir a los miembros heredados, sus propios atributos y métodos
(extender la funcionalidad de la clase).
También puede
modificar los métodos heredados (especializar el comportamiento de la clase
base).
-
Una clase
derivada puede, a su vez, ser una clase base, dando lugar a una jerarquía de
clases. LOS MODIFICADORES DE ACCESO
MODIFICADORES DE ACCESO JAVA
ACESO EN
|
||||
MODIFICADOR
|
PROPIA CLASE
|
PACKAGE
|
CLASE DERIVADA
|
RESTO
|
private
|
SI
|
NO
|
NO
|
NO
|
<Sin modificador>
|
SI
|
SI
|
NO
|
NO
|
protected
|
SI
|
SI
|
SI
|
NO
|
public
|
SI
|
SI
|
SI
|
SI
|
HERENCIA EN JAVA. SINTAXIS
La herencia en Java se expresa mediante la palabra extends
Por ejemplo, para declarar
una clase B que hereda de una clase A:
public class B extends A{
........
}
Ejemplo de
herencia Java: Disponemos de
una clase Persona con los atributos
nif y nombre.
//Clase Persona. La clase Persona es la Clase Base
public class Persona {
private String nif;
private String nombre;
public String getNif() {
return nif;
}
public void setNif(String nif) {
this.nif = nif;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
Queremos crear
ahora una clase Alumno con los atributos nif, nombre y curso. Podemos crear la
clase Alumno como derivada de la clase Persona. La clase Alumno contendrá
solamente el atributo curso, el nombre y el nif son los heredados de Persona:
//Clase Alumno. Es la clase derivada. La clase Alumno hereda de la clase Persona
public class Alumno extends Persona{
private String curso;
public String getCurso() {
return curso;
}
public void setCurso(String curso) {
this.curso = curso;
}
}
La clase alumno hereda los atributos nombre y nif
de la clase Persona y añade el atributo propio curso. Por lo tanto:
Los atributos de la clase Alumno son nif, nombre
y curso.
Los métodos de la clase Alumno son: getNif(),
setNif(String nif), getNombre(), setNombre(String nombre), getCurso(), setCurso(String curso).
La clase Alumno aunque hereda los atributos nif y
nombre, no puede acceder a ellos de forma directa ya que son privados a la
clase Persona. Se acceden a través de los métodos heredados de la clase base.
La clase Alumno puede utilizar los componentes
public y protected de la clase Persona como si fueran propios.
Ejemplo de uso de la clase Alumno:
En una jerarquía de clases, cuando un objeto invoca a un método:
1. Se busca en su clase el método
correspondiente.
2. Si no se encuentra, se busca en su clase base.
3. Si no se encuentra se sigue buscando hacia
arriba en la jerarquía de clases hasta que el método se encuentra.
4. Si al
llegar a la clase base raíz el método no se ha encontrado se producirá un
error.
REDEFINIR MIEMBROS DE LA CLASE BASE EN LAS CLASES DERIVADAS
Redefinir o modificar métodos de la clase Base
(Override)
Los métodos heredados de la clase base se pueden redefinir (también se
le llama modificar o sobreescribir) en las clases derivadas.
El método en la clase derivada se debe escribir con el mismo nombre, el
mismo número y tipo de parámetros y el mismo tipo de retorno que en la clase
base. Si no fuera así estaríamos sobrecargando el método, no redefiniéndolo.
El método sobrescrito puede tener un modificador de acceso menos
restrictivo que el de la clase base. Si por ejemplo el método heredado es
protected se puede redefinir como public pero no como private porque sería un
acceso más restrictivo que el que tiene en la clase base.
Cuando en una clase derivada se redefine un método de una clase base, se
oculta el método de la clase base y todas las sobrecargas del mismo en la clase
base.
Si queremos acceder al método de la clase
base que ha quedado oculto en la clase derivada utilizamos:
super.metodo();
Si se quiere evitar que un método de la clase Base sea
modificado en la clase derivada se debe declarar como método final. Por ejemplo:
public final void metodo(){
........
}
Ejemplo:
Vamos
a añadir a la clase Persona un método leer() para introducir por teclado los
valores a los atributos de la clase. La clase Persona queda así:
public class Persona {
private String nif;
private String nombre;
public String getNif() {
return nif;
}
public void setNif(String nif) {
this.nif = nif;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
public void leer(){
Scanner sc = new Scanner(System.in);
System.out.print("Nif: ");
nif = sc.nextLine();
System.out.print("Nombre: ");
nombre = sc.nextLine();
}
}
La clase Alumno que es derivada de Persona, heredará este método leer()
y lo puede usar como propio.
Podemos crear un objeto Alumno:
Alumno a = new Alumno();
y utilizar este método:
a.leer();
pero utilizando este método solo se leen por teclado el nif y el nombre.
En la clase Alumno se debe sobreescribir o modificar este método heredado para
que también se lea el curso. El método leer() de Alumno invocará al método
leer() de Persona para leer el nif y nombre y a continuación se leerá el curso.
La clase Alumno
con el método leer() modificado queda así:
//Clase Alumno. Clase derivada de Persona
public class Alumno extends Persona{
private String curso;
public String getCurso() {
return curso;
}
public void setCurso(String curso) {
this.curso = curso;
}
@Override //indica que se modifica un método heredado
public void leer(){
Scanner sc = new Scanner(System.in);
super.leer(); //se llama al método leer de Persona para leer nif y nombre
System.out.print("Curso: ");
curso = sc.nextLine(); //se lee el curso
}
}
Como se ha dicho antes, cuando en una clase derivada se redefine un método de una
clase base, se oculta el método de la clase base y todas las sobrecargas del
mismo en la clase base. Por eso para ejecutar el método leer() de Persona se
debe escribir super.leer();
Redefinir atributos de la clase Base
Una clase derivada puede volver a declarar un atributo heredado
(Atributo public o protected en la clase base). En este caso el atributo de la
clase base queda oculto por el de la clase derivada.
Para acceder a un atributo de la clase base que ha quedado oculto en la
clase derivada se escribe: super.atributo;
CONSTRUCTORES Y HERENCIA EN JAVA. CONSTRUCTORES EN CLASES DERIVADAS
Los constructores no se heredan. Cada clase derivada tendrá sus propios
constructores.
La clase base es la encargada de inicializar sus atributos.
La clase derivada se encarga de inicializar solo los suyos.
Cuando se crea un objeto de una clase derivada se ejecutan los
constructores en este orden:
1. Primero se ejecuta el constructor de la clase base
2. Después se ejecuta el constructor de la clase derivada.
Esto
lo podemos comprobar si añadimos a las clases Persona y Alumno sus
constructores por defecto y hacemos que cada constructor muestre un mensaje:
public class Persona {
private String nif;
private String nombre;
public Persona() {
System.out.println("Ejecutando el constructor de Persona");
}
/////// Resto de métodos
}
public class Alumno extends Persona{
private String curso;
public Alumno() {
System.out.println("Ejecutando el constructor de Alumno");
}
/////// Resto de métodos
}
Si creamos un objeto Alumno:
Alumno a = new Alumno();
Se
muestra por pantalla:
Ejecutando el constructor de Persona
Ejecutando el constructor de Alumno
Cuando se invoca al
constructor de la clase Alumno se invoca automáticamente al constructor de la
clase Persona y después continúa la ejecución del constructor de la clase
Alumno.
El constructor por defecto de la clase derivada llama al
constructor por defecto de la clase base.
La instrucción para invocar
al constructor por defecto de la clase base es: super();
Todos los constructores en las
clases derivadas contienen de forma implícita la instrucción super() como
primera instrucción.
public Alumno() {
super(); //esta instrucción se ejecuta siempre. No es necesario escribirla
System.out.println("Ejecutando el constructor de Alumno");
}
Cuando
se crea un objeto de la clase derivada y queremos asignarle valores a los
atributos heredados de la clase base:
-
La clase derivada debe tener un constructor con
parámetros adecuado que reciba los valores a asignar a los atributos de la
clase base.
-
La clase base debe tener un constructor con
parámetros adecuado.
-
El constructor de la clase derivada invoca al
constructor con parámetros de la clase base y le envía los valores iniciales de
los atributos. Debe ser la primera instrucción.
-
La clase base es la encargada de asignar valores
iniciales a sus atributos.
-
A continuación el constructor de la clase
derivada asigna valores a los atributos de su clase.
Ejemplo: en
las clases Persona y Alumno anteriores añadimos constructores con parámetros:
Ahora se pueden crear objetos
de tipo Alumno y asignarles valores iniciales. Por ejemplo:
Alumno a = new
Alumno("12345678-Z","Eliseo Gonzáles
Manzano","1DAW");
CLASES FINALES
Si
queremos evitar que una clase tenga clases derivadas debe declararse con el
modificador final
delante de class:
public final class A{
........
}
Esto la convierte en clase
final. Una clase final no se puede heredar.
Si
intentamos crear una clase derivada de A se producirá un error de compilación:
public class B extends A{ //extends A producirá un error de compilación
........
}
CLASES ABSTRACTAS
Una clase abstracta es una clase que NO se puede instanciar, es
decir, no se pueden crear objetos de esa clase.
Se diseñan solo para que otras clases hereden de ella.
La clase abstracta normalmente es la raíz de una
jerarquía de clases y contendrá el comportamiento general que deben tener todas
las subclases. En las clases derivadas se detalla la implementación.
Las clases abstractas:
- Pueden contener cero o más métodos abstractos.
- Pueden contener métodos no abstractos.
- Pueden contener atributos.
Todas las clases que hereden de una clase
abstracta deben implementar todos los
métodos abstractos heredados.
Si una clase derivada de una clase abstracta no
implementa algún método abstracto se convierte en abstracta y
tendrá que declararse como tal (tanto la clase como los métodos que siguen
siendo abstractos).
Aunque no se pueden crear objetos de una clase
abstracta, sí pueden tener constructores para inicializar sus atributos que serán
invocados cuando se creen objetos de clases derivadas.
La forma general de declarar una clase abstracta en Java es:
[modificador] abstract class nombreClase{
.......
}
Ejemplo de clase Abstracta en Java: Clase Polígono.
//Clase abstracta Poligono
public abstract class Poligono {
private int numLados;
public Poligono() {
}
public Poligono(int numLados) {
this.numLados = numLados;
}
public int getNumLados() {
return numLados;
}
public void setNumLados(int numLados) {
this.numLados = numLados;
}
//Declaración del método abstracto area()
public abstract double area();
}
La clase Poligono contiene
un único atributo nublados. Es una
clase abstracta porque contiene el método abstracto area().
A partir de la clase Poligono
vamos a crear dos clases derivadas Rectangulo y Triangulo. Ambas deberán
implementar el método area(). De lo contrario también serían clases abstractas.
El diagrama UML es el
siguiente:
En UML las clases
abstractas y métodos abstractos se escriben con su nombre en cursiva.
//Clase Rectangulo
public class Rectangulo extends Poligono{
private double lado1;
private double lado2;
public Rectangulo() {
}
public Rectangulo(double lado1, double lado2) {
super(2);
this.lado1 = lado1;
this.lado2 = lado2;
}
public double getLado1() {
return lado1;
}
public void setLado1(double lado1) {
this.lado1 = lado1;
}
public double getLado2() {
return lado2;
}
public void setLado2(double lado2) {
this.lado2 = lado2;
}
//Implementación del método abstracto area()
//heredado de la clase Polígono
@Override
public double area(){
return lado1 * lado2;
}
}
//Clase Triangulo
public class Triangulo extends Poligono{
private double lado1;
private double lado2;
private double lado3;
public Triangulo() {
}
public Triangulo(double lado1, double lado2, double lado3) {
super(3);
this.lado1 = lado1;
this.lado2 = lado2;
this.lado3 = lado3;
}
public double getLado1() {
return lado1;
}
public void setLado1(double lado1) {
this.lado1 = lado1;
}
public double getLado2() {
return lado2;
}
public void setLado2(double lado2) {
this.lado2 = lado2;
}
public double getLado3() {
return lado3;
}
public void setLado3(double lado3) {
this.lado3 = lado3;
}
//Implementación del método abstracto area()
//heredado de la clase Polígono
@Override
public double area(){
double p = (lado1+lado2+lado3)/2;
return Math.sqrt(p * (p-lado1) * (p-lado2) * (p-lado3));
}
}
Ejemplo
de uso de las clases:
public static void main(String[] args) {
Triangulo t = new Triangulo(3.25,4.55,2.71);
System.out.printf("Área del triángulo: %.2f %n" , t.area());
Rectangulo r = new Rectangulo(5.70,2.29);
System.out.printf("Área del rectángulo: %.2f %n" , r.area());
}
CASTING: CONVERSIONES ENTRE CLASES
UpCasting: Conversiones implícitas
La herencia establece una relación ES UN
entre clases. Esto quiere decir que un objeto de una clase derivada es también
un objeto de la clase base.
Por esta razón:
Se puede asignar de forma implícita una
referencia a un objeto de una clase derivada a una referencia de la clase base.
Son tipos
compatibles.
También se llaman conversiones ascendentes o upcasting.
En el ejemplo anterior un triángulo en un
Poligono y un cuadrado es un Poligono.
Poligono p;
Triangulo t = new
Triangulo(3,5,2);
Como un triángulo es un polígono, se puede hacer
esta asignación:
p = t;
La variable p de tipo Poligono puede contener la
referencia de un objeto Triangulo ya que son tipos compatibles.
Cuando manejamos un objeto a través de una referencia a una superclase
(directa o indirecta) solo se pueden ejecutar métodos disponibles en la superclase.
En el ejemplo, la instrucción
p.getLado1();
provocará un error ya que p es de tipo Poligono y
el método getLado1() no es un método de esa clase.
Cuando manejamos un objeto a través de una referencia a una superclase
(directa o indirecta) y se invoca a un método que está redefinido
en las subclases se ejecuta el método de la clase a la que pertenece el
objeto no el de la referencia.
En el ejemplo, la instrucción
p.area();
ejecutará el método area() de Triángulo.
DownCasting: Conversiones explícitas
Se puede asignar una referencia de la clase base
a una referencia de la clase derivada, siempre que la referencia de la clase
base sea a un objeto de la misma clase derivada a la que se va a asignar o de
una clase derivada de ésta.
También se llaman conversiones descendentes o downcasting.
Esta conversión debe hacerse mediante un casting.
Siguiendo con el ejemplo anterior:
Poligono p = new
Triangulo(1,3,2); //upcasting
Triangulo t;
t = (Triangulo) p; //downcasting
Esta asignación se puede hacer porque p contiene
la referencia de un objeto triángulo.
Las siguientes instrucciones provocarán un error
de ejecución del tipo ClassCastException:
Triangulo t;
Poligono p1 = new Rectangulo(3,2);
t = (Triangulo)p1; //----> Error de
ejecución
p1 contiene la referencia a un objeto Rectangulo
y no se puede convertir en una referencia a un objeto Triangulo. No son tipos
compatibles.
EL OPERADOR instanceof
Las operaciones entre clases y en particular el
downcasting requieren que las clases sean de tipos compatibles. Para
asegurarnos de ello podemos utilizar el operador instanceof.
instanceof devuelve true si el objeto es
instancia de la clase y false en caso contrario.
La sintaxis es:
Objeto instanceof Clase
Ejemplo:
Triangulo t;
Poligono p1 = new Rectangulo(3,2);
if(p1 instanceof Triangulo){
t = (Triangulo)p1;
}else{
System.out.println("Objetos incompatibles");
}
me fue muy util
ResponderEliminargracias enrique, otra vez utilizando tus apuntes, después de estudiar programación en el instituto en tu clase, ahora en la universidad, también hay una asignatura de java.
ResponderEliminarGracias por el comentario, Espero que te vaya muy bien en la Universidad, pero... me gustaría saber quién eres!!???? Un saludo
EliminarChamit
EliminarMuchas gracias muy bien explicado, entendi a la perfeccion. Más personas como vos.
ResponderEliminares de gran ayuda la informacion que han aportado en este blog puesto que esta muy explicito y ayuda a entender dicho tema
ResponderEliminarMe alegro que te haya sido útil Eduardo. Saludos y gracias por seguir el blog
Eliminarhola amigo me puedes facilitar el codigo
ResponderEliminarjoencepe77@gmail.com
A mi igual si me pudieras facilitar el código me ahorrarías mucho tiempo.
ResponderEliminartonyserrano1122@gmail.com
Saludos.
Llevo poco estudiando Java y, el tema de la herencia, me parece de lo más complicado que he visto, no tanto entender la teoría, más bien implementar la herencia en nuestras clases. Gracias a estas pocas líneas del maestro Enrique, puedo ver un poco de luz en este tema. Gracias, gracias, gracias.
ResponderEliminarGracias a tí Iban por el comentario y por seguir el blog ;)
EliminarHola henrique gracias por el curso.me ayude mucho a entender muchas cosas.una pregunta pork pones super(2) y super(3)?geacias
ResponderEliminarHola Marley, me alegro de que el curso te sea útil.
EliminarEn el constructor de la clase Rectángulo, super(2) es una llamada al constructor con parámetros de la clase base Poligono. El 2 es el valor correspondiente a la variable numLados que para un rectángulo es 2. En la clase Triangulo la llamada al contructor de Poligono es super(3) ya que el valor de la variable numLados en este caso es 3.