No es un bug, es una característica no documentada

martes, 31 de marzo de 2015

Programación. Interacción entre clases (II)

2:11 Posted by Inazio , No comments

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 comentarios:

Publicar un comentario