La sobreescritura de métodos
La herencia nos permite ampliar la funcionalidad de la superclase en
las subclases simplemente añadiendo nuevos métodos.
Pero también nos permite reescribir los métodos heredados adaptándolos
a las necesidades de las subclases.
Este mecanismo, propio de la orientación a objetos, es muy útil para
refinar en las subclases un comportamiento predefinido en la superclase.
Ejemplo. En Felino
tengo un método que es
public void hacerRuido(){
System.out.println(“Elige
al feline concreto”);
}
En la clase Tigre sería
public void hacerRuido(){
System.out.println(rugir());
}
Y en la clase Gato
public void hacerRuido(){
System.println(maullar());
}
Reglas para la sobreescritura de métodos
Sólo se puede sobreescribir los métodos heredados de la superclase.
El nuevo método debe mantener el mismo prototipo (nombre, parámetros de
entrada y salida).
El método sobrescrito puede modificar la visibilidad del método
original, pero siemper ha de hacerlo más public.
Ejemplo. Se puede
pasar de protected a public, pero no de protected a private-
La sobrescritura de propiedades
Al igual que podemos sobrescribir métodos ¿podríamos sobrescribir las
propiedades heredadas? Sí, solo que se habla de ocultar o ensombrecer
propiedades.
Si una propiedad de la subclase se llama igual que otra que hereda la
superclase, ésta última queda oculta, no pudiéndose accede desde la subclase.
Este mecanismo no se suele usar.
Los constructores y la herencia
Los constructores no se heredan, sin embargo cuando invocamos un
constructor para crear un objeto de la superclase, éste llama a un constructor
de una superclase.
Se produce entonces una cadena de llamadas a constructores que a veces
es necesario supervisor.
Al igual que vimos la “autotransferencia” this, ahora vamos a
estudiar la referencia al objeto padre, super.
Super se puede utilizer para referirnos a una propiedad o método de la
clase padre del objeto actual.
Ejemplo
super.caza();
También para llamar a un constructor de la clase padre del objeto
actual. Sintáxis:
super(); // Llama al constructor sin
parámetros
super(lista de parámetros); // Llama a un
constructor con parámetros de la clase padre
Está prohibido encadenar dos super:
super.super.nace();
super.super();
Decíamos que el constructor de una subclase llama al de la supeclase.
Cuando el compilador se encuentra con un constructor hace la siguiente
suposición:
è
Todas las clases tienen una versión del constructor
sin parámetros
Esto no siempre es verdad, sin embargo, el compilador añade super(); como primera línea al compilar un constructor.
Esto puede provocar errores de compilación.
Esto es un error de compilación. Se intent llamar al constructor Cuenta() que no existe en la superclase.
Se arregla si nosotros escribimos como primera línea la llamada al
constructor correcto.
public CuentaCorriente(int numc, String
titular, double interes){
super(numC,
titular);
this.interes
= interes;
}
La raíz de la jerarquía
Los constructores se llaman unos a otros ascendiendo en la jerarquía de
clases. ¿Hasta donde ascendemos? Hasta la clase Object.
Object es la
superclase de la que heredan (directamente o indirectamente) todas las clases
Object es el
núcleo de todos los objetos. Si por ejemplo hacemos
Felino F = new Felino();
¿Y tú de quién eres? El operador instanceof
Sirve para averiguar si una referencia a un objeto apunta a una
instancia de una clase o a alguna de sus superclases. La sintaxis es
referencia instanceof NombreClase à devuelve boolean
Con el objeto del ejemplo anterior
Felino f = new Felino();
if (f instanceof Felino) à return true;
if (f instanceof Mamifero) à return true;
if (f instanceof Object) à return true;
if (f instanceof String) à return false;
Algunos métodos heredados de Object
public boolean equals(Object obj) sirve
para comparer el objeto actual con el que se recibe como parámetro.
Internamente se define como return this
== obj;
Devuelve cierto si la referencia obj apunta al objeto actual.
Tal y como está definido sirve para poco, así que se suele
sobreescribir para que compare contenidos.
Ejemplo. Se
podría comparer dos Coches de forma que se consideran igual si son de la misma
marca y modelo.
public String toString() devuelve
una cadena que describe el estado del objeto.
Devuelve una cadena con el nombre de la clase a la que pertenece el
objeto y la dirección de memoria en la que está alojado (en hexadecimal)
NombreClase@direcciónMemoriaHexadecimal
También se suele sobreescribir adaptándolo a las necesidades de la
clase.
Ejemplo. Si lo
sobreescribimos en la clase CuentaBancaria podríamos devolver una cadena con el
número de cuenta, el nombre del titular, el saldo y si está o no bloqueada.
El modificaor final
Queremos que un método de una superclase no pueda ser sobreescrito en
ninguna de sus subclases. Necesitamos una version final del método.
Sintaxis
… final tipoRetorno nombreMetodo(parámetros)
Si intentamos sobreescribir un método heredado declarado como final el
compilador nos dará un error.
Si queremos que una clase no pueda ser extendida podemos indicarle al
compilador que es una version final.
Sintaxis
… final class NombreClase{ … }
Si intentamos crear una subclase declarada como final obtendremos un
error del compilador.
El modificador abstract
Si queremos que de una clase NO se puedan crear objetos pero que sí
puedan crearse subclases de ella debemos declararla como abstract.
Sintaxis
… abstract class NombreClase{ … }
Si intentamos crear un objeto de una clase abstracta el compilador nos
dará un error.
A los métodos de una clase abstracta les podemos dar código o no, en éste
ultimo caso también habría que declararlos como abstractos.
Sintaxis
… abstract tipoRetorno nombreMetodo (parámetros);
Las subclases de una clase abstracta están obligadas a sobrescribir
dichos métodos o también declararlos como abstractos.
Si una clase posee al menos un método abstracto es obligatorio
declararla como abstracta.
Sin embargo, una clase abstracta puede no tener ningún método
abstracto.
Los constructors no pueden ser declarados como abstractos. Si
escribimos alguno tenemos que darle código.
Utilidad práctica
Hay clases que, dada su semántica, no es muy coherente poder crear
objetos de ella, como por ejemplo Felino, Mamifero, Vehiculo…
Las clases abstractas las podríamos asemejar a un contrato que deben
cumplir todas sus subclases.
El contrato sería como una lista de tareas, de las cuales algunas están
hechas y otras por hacer (abstractas).
Esta forma de diseñar una clase y su descendencia facilita el trabajo
en equipo.
Por ejemplo, el jefe de un proyecto podría diseñar la clase abstracta y
los programadores desarrollar las subclases.
Los castings
La conversion de tipos o casting
Sabemos que hay dos grupos de tipos de datos en Java, los primitivos y
las referencias a objetos.
De otro lado, cada expresión del lenguaje nos devuelve un resultado de
un tipo, con lo que podríamos hablar del tipo de una expresión:
contador > 10 return boolean;
2 * 3.1416 * radio return double;
A veces nos interesa cambiar el tipo de una expresión. Esta conversion de
tipo recibe el nombre de casting
La conversión de tipos primitivos
Las conversions se dividen en dos grupos:
Sin pérdida de
información
El tipo de destino es “de mayor capacidad” que el de origen
float f = 3.35345345345;
double d;
d = f;
Con pérdida de
información
El tipo de destino es “de menor capacidad” que el de origen.
double d = 3.3534534533345654651813215;
float f;
f = d; // Provoca un error o advertencia del
compilador
Las conversiones sin pérdida de información no molestan al compilador, éste
añade automáticamente el código necesario para la conversión.
Sin embargo las conversions con pérdida de información son
responsabilidad del programador y es éste quien debe añadir el código para la
conversión.
Operador de casting (tipo de destino). Expresión
float f = (float) 2 * 3.1415 * radio;
Funciona igual que en lenguaje C.
Las conversions de referencias a objetos
Supongamos que tenemos la siguiente jerarquía de clases
¿Qué ocurre si escribimos lo siguiente?
Tigre t1 = new Tigre();
Felino f = t1; // Sin pérdida de información
Tigre t2 = (Tigre) f; // Con pérdida de
información
Las conversiones entre referencias de objetos también las podemos
agrupar en :
è
Sin pérdida
de información o upcasting. Son aquellas en las que la referencia al
objeto se converte a alguna de las superclass. Son las que ascienden por
jerarquía.
è
Con pérdida
de información o downcasting. En este caso la referencia al objeto se
convierte a alguna de las subclases. Son las que descienden por la jerarquía.
Al igual que en los tipos primitivos, las conversiones con pérdida de
información (hacía abajo) son responsabilidad del programador, que debe incluir
el operador de casting descrito.
Se suele acompañar con el operador instanceof para asegurarnos
de que la conversión va a ser posible.
Tigre t1 = new Tigre();
Felino f = t1; // Casting hacía arriba. Sin
problemas
if (f instanceof Tigre)
Tigre
t2 = (Tigre) f; // Casting hacía abajo. Precaución…
Si una conversión no es posible se lanza una excepción ClassCastException
en tiempo de ejecución y se termina el problema.
El polimorfismo
Es una característica propia de la orientación a objetos y se deriva
del uso de la herencia y el casting de referencias.
El principio del polimorfismo dice que todo objeto puede ser utilizado
como un objeto de la clase a la que pertenece pero también como un objeto
cualquiera de sus superclases (incluso si son abstractas).
Ejemplo
Tigre ti = new Tigre();
Felino fe = ti;
fe.caza(); // Bien, todos los
cazan
fe.hacRuido(); // Bien, todos
los felinos hacen ruido
fe.ruge(); // Error, no todos
los felinos rugen
ti.ruge(); // Bien, los tigres
sí rugen
¿Qué ocurre si hay métodos sobrescritos?
Con fe.haceRuido(), ¿cuál
ejecuta?
El de la clase Tigre. Un objeto nunca olvida de que clase es.
Serviría para, por ejemplo, hacer un vector de felinos e indicar que
hagan todo ruido, cada feline (tigre, gato…) hará su ruido característico…
Se utiliza cuando nos interese:
è
Realizar
una generalización, olvidando los detalles concretos de uno o varios
objetos de distintas clases y buscar un ancestro común a todos ellos, por
ejemplo para escribir un método que pueda recibir objetos de multiples clases
…
public void manejarFelinos(Felino f){
f.caza();
f.haceRuido();
}
…
è
Si
necesitamos almacenar varios objetos de multiples clases en una
estructura de datos
…
Felino reservaFelinos[];
reservaFelinos[0] = new Tigre();
reservaFelinos[1] = new Leon();
…
El polimorfismo es una herramienta muy potente y utiliza de la
programación de sistemas orientados a objetos.
Las interfaces de Java
Concepto
Una clase abstracta la definimos como una clase de la que no se pueden
crear objetos y cuyos métodos pueden no tener implementación.
Si llevamos el concepto de las clases abstractas al extreme aparecen
las interfaces de Java.
Una interfaz es una clase abstracta en la que:
è
Todos sus métodos son públicos y abstractos
è
No pueden existir ni atributos ni constructors
Las interfaces sirven para especificar las operaciones que
obligatoriamente deben implementar un conjunto de clases.
Una interfaz es comparable con un contrato. Si una clase implementa una
interfaz (se adhiere al contrato) está obligada a dar código a todos los métodos
que en ésta aparezcan.
Las clases que implementan una interfaz no heredan de ella. Aparece una
nueva forma de interacción entre clases e interfaces, la relación de
implementación (implements).
Notación UML para
implementar la interfaz
El nombre de las interfaces suele expresar una capacidad de los objetos
que las implementan, como Comparable, Clonable, CapazDeCorrer,
CapazDeEscuchar…
Declaración de una
interfaz
Para declarer una interfaz escribimos
public interface CapazDeVolar{
void
despegar(); // Ya se asumen como públicos
void
atterizar(); // y abstractos
void
volar();
}
De cara a la compilación una interfaz se comporta como cualquier otra
clase
è
Se escribe en un .java y el compilador crea un
.class
è
Se puede incluir en un paquete
Implementación de
una interfaz
Para que una clase implemente una interfaz
public class Helicoptero implements
CapazDeVolar{
//
Debe dar código a los métodos de la interfaz
}
La herencia y la implementación son independientes entre sí. De modo
que una clase puede heredar de otra y además implementar una o más interfaces.
public class B extends A implements Inter1,
Inter2{ … }
La herencia de
interfaces
Las interfaces pueden heredar unas de otras formando una jerarquía.
En Java escribiríamos
public interface CapazDeVolar extends
CapazDeMoverse{
void
despegar();
void
aterrizar();
void
volar();
}
En el caso de CapazDeVolar hereda la
interfaz CapazDeMoverse, esto implica que
una clase que implemente la interfaz CapazDeVolar debe dar
código a los métodos de ambas interfaces.
è
Una interfaz puede heredar de una o más interfaces
è
Una clase puede heredar solo de una clase pero
puede implementar más de una interfaz
Las interfaces y el
polimorfismo
Con el polimorfismo vimos que un objeto podíamos apuntarlo con
referencias de cualquiera de sus superclass y usarlo como si fuera un objeto de
éstas.
Ahora añadimos las interfaces al polimorfismo de modo que un objeto
puede ser apuntado mediante una referencia y ser utilizado como si fuera un
objeto de ésta.
Helicoptero h = new helicoptero();
CapazDeVolar cdv = h;
h.arrancar();
cdv.despegar();
cdv.volar();
cdv.aterrizar();
cdv.parar(); // Error, parar no pertenece a
la interfaz
h.parar();
Utilidades de las
interfaces
Las interfaces son una herramienta excelente para el diseño de
sistemas, permitiendo describer la funcionalidad de un conjunto de clases de
forma sencilla, compacta y portable.
Ayudan al trabajo en paralelo de distintos equipos de desarrollo y
mejora de comunicación entre programadores y diseñadores.
Hace falta un poco de práctica para familiarizarse con las interfaces.
Son las estructuras más abstractas del lenguaje Java y, por tanto, es más
dificil comprender su utilidad.
0 comments:
Publicar un comentario