Tema 3 Herencia en Java. Programación Orientada a Objetos Curso 2008/2009

134  Descargar (0)

Texto completo

(1)

Tema 3 – Herencia en Java

Programación Orientada a Objetos Curso 2008/2009

(2)

Contenido

• Introducción. • Definición y tipos. • Constructores. • Redefinición. • Restringir la herencia. • Visibilidad protegida. • Polimorfismo. • Herencia y sistema de tipos. • Ligadura dinámica. • Clase Object. • Código genérico. • Casting • Opeador instanceof • Clases abstractas. • Copia de objetos. • Igualdad de objetos. • Interfaces. • Genericidad. • Acciones. • Clases anónimas. • Iteradores. • Clases internas. • Colecciones. • Herencia múltiple.

(3)

Introducción

† Las clases no son suficientes para conseguir los objetivos de:

† Reutilización

Æ Necesidad de mecanismos para definir

código genérico

„ Capturar aspectos comunes en grupos de estructuras

similares.

„ Independencia de la implementación y representación.

† Extensibilidad:

(4)

Introducción

† Entre las clases pueden existir relaciones

conceptuales:

Extensión, Especialización, Combinación. † Ejemplos:

„ “Una pila puede definirse a partir de una cola o

viceversa”

„ “Un rectángulo es una especialización de polígono” „ “Libros y Revistas tienen propiedades comunes”

† Herencia:

„ Mecanismo para definir y utilizar estas relaciones. „ Permite la definición de una clase a partir de otra.

(5)

Introducción

† La herencia organiza las clases en una estructura jerárquica Æ Jerarquía de clases

† No es sólo un mecanismo de reutilización de código.

† Es consistente con el sistema de tipos. † Ejemplos:

PUBLICACION

LIBRO REVISTA

LIBRO_TEXTO INVESTIGACION MAGAZINE

FIGURA

POLIGONO CIRCULO RECTANGULO

(6)

Herencia

† Si la clase B hereda de A, entonces B incorpora la estructura (atributos) y comportamiento

(métodos) de la clase A, pero puede incluir

adaptaciones:

„ B puede añadir nuevos atributos. „ B puede añadir nuevos métodos.

„ B puede redefinir métodos heredados (refinar o

reemplazar).

† En general, las adaptaciones dependen del Lenguaje OO.

(7)

Herencia

A

B

C

† B hereda de A (A es la superclase y B la subclase) † A es la clase padre o clase base de B

† C hereda de B y A

† B y C son subclases de A

† B es un descendiente directo de A † C es un descendiente indirecto de A † A y B son ascendientes de C

(8)

Tipos de herencia

† Herencia simple:

„ Una clase puede heredar

de una única clase.

„ Java, C#

† Herencia múltiple:

„ Una clase puede heredar

de varias clases. „ C++ A E D C B A B C

(9)

Reconocer la herencia

† Generalización (factorización):

„ Se detectan dos clases con características en común

y se crea una clase padre con esas características.

„ Ejemplo: Libro, Revista Æ Publicación

† Especialización:

„ Se detecta que una clase es un caso especial de otra. „ Ejemplo: Rectángulo es un tipo de Polígono.

† No hay receta mágica para crear buenas jerarquías de herencia.

(10)

Caso de estudio

† Continuación de la aplicación de gestión

bancaria.

† Se introduce el concepto Depósito que permite a un cliente obtener intereses por su dinero.

† Un Depósito, simplificado, se define:

„ Propiedades: titular, capital, plazo en días y tipo de

interés.

„ Comportamiento:

† Permite liquidar el depósito devolviendo el capital

depositado más los intereses.

† Permite consultar los intereses generados al

(11)

Clase Depósito 1/2

public class Deposito {

private Cliente titular;

private double capital;

private int plazoDias;

private double tipoInteres;

public Deposito(Cliente titular, double capital,

int plazoDias, double tipoInteres) {

this.titular = titular;

this.capital = capital;

this.plazoDias = plazoDias;

this.tipoInteres = tipoInteres; }…

(12)

Clase Depósito 2/2

public class Deposito {

public double liquidar() {

return getCapital() + getIntereses(); }

public double getIntereses() {

return (plazoDias * tipoInteres * capital)/365; }

public double getCapital() { … }

public int getPlazoDias() { … }

public double getTipoInteres() { … }

public Cliente getTitular() { … } }

(13)

Caso de Estudio

† Identificamos el concepto Depósito

Estructurado que se caracteriza por tener una parte del capital a tipo fijo y otra parte a tipo

variable.

† ¿Depósito Estructurado es un Depósito?

„ Comparte todas las características de Depósito. „ Añade nuevas características.

† La clase DepositoEstructurado hereda de

Deposito:

„ Nuevas características: capital a tipo variable y el tipo

(14)

Clase Depósito Estructurado

public class DepositoEstructurado extends Deposito {

private double tipoInteresVariable;

private double capitalVariable;

public DepositoEstructurado(…) {…}

public double getInteresesVariable() {

return (getPlazoDias() * tipoInteresVariable

* capitalVariable)/365; }

public void setTipoInteresVariable(…

public double getTipoInteresVariable() { … }

public double getCapitalVariable() { … } }

(15)

Clase Depósito Estructurado

† La subclase incluye las características

específicas:

„ Atributos tipo de interés variable y capital a tipo variable. „ Método de consulta (get) del capital variable.

„ Métodos de consulta/establecimiento (get/set) del tipo

de interés variable.

„ Método para el cálculo de los intereses del capital a tipo

variable.

† La clase hija hereda todos los atributos de la

clase padre, aunque no los vea debido a la ocultación de la información.

† La clase dispone de los métodos heredados como si fueran propios (ejemplo, getPlazoDias()).

(16)

Herencia y constructores

† En Java, los constructores no se heredan.

† El constructor de un DepositoEstructurado se declara con los mismos parámetros que un

Deposito y añade la cantidad de capital a tipo

variable.

† La clase DepositoEstructurado no tiene

visibilidad sobre los atributos privados de Deposito.

† Java permite invocar a los constructores de la

clase padre dentro de un constructor utilizando

(17)

Clase DepositoEstructurado

public class DepositoEstructurado extends Deposito {

public DepositoEstructurado(Cliente titular,

double capitalFijo, double capitalVariable,

int plazoDias, double tipoInteresFijo) {

super(titular, capitalFijo, plazoDias, tipoInteresFijo);

this.capitalVariable = capitalVariable; }

}

(18)

Herencia y constructores

† Cuando se aplica herencia, la llamada a un

constructor de la clase padre es obligatoria. † Debe ser la primera sentencia del código del

constructor.

† Si se omite la llamada, el compilador asume que la primera llamada es super()

Î Llama al constructor sin argumentos de la clase padre.

Î Si no existe ese constructor, la clase no compila.

(19)

Adaptación del código heredado

† La clase DepositoEstructurado añade

nuevas propiedades: capital a tipo variable y tipo de interés variable.

† Sin embargo, hay que adaptar dos métodos

heredados:

„ getCapital(): el capital de un depósito estructurado es el capital a tipo fijo más el capital a tipo variable.

„ getIntereses(): intereses a tipo fijo más intereses a tipo variable.

† La herencia permite la redefinición de métodos para adaptarlos a la semántica de la clase.

(20)

Redefinición de métodos

† La redefinición de un método heredado puede ser de dos tipos:

„ Refinamiento: se añade nueva funcionalidad al

comportamiento heredado.

„ Reemplazo: se sustituye completamente la

implementación del método heredado.

† En el refinamiento de un método resulta útil

(21)

Redefinición de métodos y super

† El lenguaje proporciona la palabra reservada

super que permite llamar a la versión del padre de un método que se redefine en la clase.

† En general, el uso de super se recomienda para el refinamiento de métodos.

Î No hay que utilizar super para llamar a

métodos que se heredan.

† Sin embargo, hay excepciones

„ getCapital(): retorna el capital total del depósito.

„ La versión del padre (super.getCapital())

devuelve la parte del capital a tipo fijo. Puede resultar útil en la implementación de la clase.

(22)

Clase DepositoEstructurado

public class DepositoEstructurado extends Deposito { ...

@Override

public double getIntereses() {

return super.getIntereses() + getInteresesVariable(); }

@Override

public double getCapital() {

return super.getCapital() + getCapitalVariable(); }

}

(23)

Caso de estudio

† Identificamos un nuevo tipo de depósito formado por una parte del capital a tipo fijo y otra a tipo variable con un interés mínimo garantizado

(Depósito Garantizado).

† Este depósito posee todas las características de un Depósito Estructurado y añade una garantía al interés variable (regla es un).

† La clase DepositoGarantizado hereda de

(24)

Clase DepositoGarantizado

public class DepositoGarantizado

extends DepositoEstructurado {

private double tipoInteresGarantizado;

public DepositoGarantizado(…, {

super(titular, capitalFijo, capitalVariable, plazoDias, tipoInteresFijo);

this.tipoInteresGarantizado = tipoIntersesGarantizado; setTipoInteresVariable(tipoIntersesGarantizado);

} }

(25)

Redefinición de métodos

† Esta clase debe controlar que al establecer el tipo de interés variable no quede por debajo del interés garantizado.

† Para ello, redefine (refina) el método

setTipoInteresVariable().

@Override

public void setTipoInteresVariable(

double tipoInteresVariable) {

if (tipoInteresVariable >= tipoInteresGarantizado)

super.setTipoInteresVariable(tipoInteresVariable); }

(26)

Caso de estudio

† Un Depósito Penalizable es un depósito básico en el que los intereses pueden ser penalizados. † El nuevo depósito tiene todas las características

de un depósito básico (regla es un).

Î Clase DepositoPenalizable hereda de

Deposito

† La penalización consiste en reducir el interés del depósito a la mitad.

† Adapta el cálculo de los intereses según la penalización.

(27)

Clase DepositoPenalizable

public class DepositoPenalizable extends Deposito {

private boolean penalizado;

public DepositoPenalizable(…) {

super(titular, cantidad, plazoDias, tipoInteres);

penalizado = false; }

@Override

public double getIntereses() {

if (penalizado)

return super.getIntereses() / 2;

else return super.getIntereses(); }

public boolean isPenalizado() {…}

(28)

Redefinición

† En los ejemplos se comprueba que la redefinición reconcilia la reutilización con la extensibilidad:

„ Es habitual hacer cambios cuando se reutiliza un

código.

† Los atributos no se pueden redefinir, sólo se

ocultan:

„ Si la clase hija define un atributo con el mismo nombre

que un atributo de la clase padre, éste no es accesible.

„ El atributo del padre existe, pero no se puede ver.

† Un método es una redefinición si tiene la misma signatura (nombre y parámetros) que un método de la clase padre:

„ Si se cambia el tipo de los parámetros se está

(29)

Jerarquía de Herencia

Deposito

DepositoEstructurado

DepositoGarantizado

(30)

Restringir la herencia

† ¿Es seguro permitir que los subtipos redefinan cualquier método?

† La redefinición incorrecta del algoritmo del método liquidar() podría comprometer la consistencia y seguridad de la aplicación.

† En Java se puede aplicar el modificador final a un método para indicar que no puede ser

redefinido.

† Asimismo, el modificador final es aplicable a una

clase indicando que no se puede heredar de ella.

† ¿El modificador final va contra el principio

(31)

Visibilidad para la herencia

† Puede tener sentido que algunos atributos y

métodos de una clase, sin ser públicos, puedan ser

accesibles a las subclases.

† Ejemplo: el atributo capital de Deposito. † Nivel de visibilidad protegido (protected):

visibilidad para las subclases y las clases del mismo paquete.

† Es discutible el uso de visibilidad protegida para

los atributos

Æ En contra de la ocultación de la información. † Para los métodos, la visibilidad protegida es útil.

(32)

Niveles de visibilidad

† En Java, los niveles de visibilidad son

incrementales

private () paquete

protected

public public Æ todo el código

protected Æ clase + paquete + subclases

(nada) Æ clase + paquete

(33)

Revisión redefinición de métodos

† Al redefinir un método podemos cambiar su implementación (refinamiento, reemplazo). † También se puede cambiar el nivel de

visibilidad de la declaración para

incrementarlo.

† Es posible cambiar el tipo de retorno a otro más específico (subclase) Æ Regla covariante.

„ Ejemplo: en DepositoGarantizado podemos

obligar a que los clientes sean de tipo

ClientePreferente, subclase de Cliente.

„ En este caso, el método de consulta se redefine como

(34)

Polimorfismo

† En un sentido amplio, polimorfismo significa que hay un nombre (variable, función o clase) y

muchos significados diferentes.

† Tipos de polimorfismo:

„ Paramétrico (genericidad). „ Ad hoc (sobrecarga).

„ De asignación (variable polimórfica). „ Puro (función polimórfica).

(35)

Polimorfismo de asignación

† Capacidad de una entidad (atributo, variable) de

referenciar en tiempo de ejecución a objetos de diferentes clases.

† Es restringido por herencia.

† Fundamental para escribir código genérico reutilizable.

† El polimorfismo implica que una entidad tiene un

(36)

Tipos estático y dinámico

† Tipo estático (te):

„ Tipo asociado a la declaración de una entidad.

† Tipo dinámico:

„ Tipo correspondiente a la clase del objeto conectado a la

entidad en tiempo de ejecución.

† Conjunto de tipos dinámicos (ctd):

„ Conjunto de posibles tipos dinámicos de una entidad.

† Ejemplo: A E D C B F

A oa; B ob; C oc;

te(oa) = A ctd(oa) = {A,B,C,D,E,F} te(ob) = B ctd(ob) = {B, D, E}

(37)

Polimorfismo de asignación

† Asignación polimórfica:

† La variable deposito tiene como tipo estático

Deposito.

† El tipo dinámico de la variable cambia:

„ Línea 1: clase Deposito.

„ Línea 3: clase DepositoPenalizable.

1. Deposito deposito = new Deposito(…); 2. DepositoPenalizable penalizable =

new DepositoPenalizable(…);

// Asignación polimórfica

(38)

Polimorfismo puro

† Método que puede recibir distintos tipos de argumentos manteniendo el mismo

comportamiento.

† La ejecución correcta se determina en tiempo de ejecución.

public double indiceRentabilidad(Deposito deposito) {

return deposito.getIntereses()/deposito.getCapital(); }

(39)

Polimorfismo puro vs. Sobrecarga

† Métodos sobrecargados ≠ Métodos

polimórficos

† Sobrecarga:

„ Dos o más rutinas comparten el mismo nombre y

distinta lista de argumentos Æ nombre polimórfico.

„ Distintas definiciones y tipos.

„ El compilador determina en tiempo de compilación la

rutina sobrecargada que se ejecuta.

† Métodos polimórficos:

„ Una única función que puede recibir una variedad de

argumentos (comportamiento uniforme).

„ La ejecución correcta se determina dinámicamente en

(40)

Polimorfismo puro vs. Sobrecarga

† Polimorfismo puro:

† Sobrecarga:

public double indiceRentabilidad(DepositoEstructurado dep) { … }

public double indiceRentabilidad(DepositoPenalizable dep) { … }

public double indiceRentabilidad(Deposito deposito) {

return deposito.getIntereses()/deposito.getCapital(); }

(41)

Herencia y sistema de tipos

† ¿Serían posibles las siguientes asignaciones?

„ Objeto DepositoEstructurado a variable Deposito. „ Objeto DepositoEstructurado a variable

DepositoPenalizable.

„ Objeto DepositoGarantizado a variable Deposito.

† Comprobación estática de tipos:

„ Reglas de consistencia comprobadas por el compilador que

garantizan que durante la ejecución no habrá violación de tipos.

† Beneficios: fiabilidad, legibilidad y eficiencia.

† Inconveniente: se aplica una política pesimista que a

(42)

Herencia y sistema de tipos

† La herencia es consistente con el sistema de

tipos.

† Sobre una variable cuyo tipo estático sea

DepositoGarantizado podemos aplicar: „ Métodos heredados: getIntereses(),

getCapital(), etc.

„ Métodos propios y redefinidos:

getTipoInteresGarantizado(), setTipoInteresVariable()

† Si el tipo estático es Deposito no podemos aplicar métodos de los subtipos:

„ deposito.getTipoInteresVariable(); //Error

(43)

Compatibilidad de tipos

† Una clase (tipo) B es compatible con otra clase A si B es descendiente de A:

„ DepositoEstructurado es compatible con Deposito.

„ DepositoGarantizado es compatible con Deposito y DepositoEstructrado.

„ DepositoEstructurado no es compatible con DepositoPenalizable.

(44)

Compatibilidad de tipos

† Una asignación polimórfica es válida si el tipo de la parte derecha es compatible con el tipo

estático de la parte izquierda:

Deposito deposito = new DepositoEstructurado(…);

DepositoPenalizable penalizable =

new DepositoPenalizable(…);

// Asignación polimórfica

(45)

Compatibilidad de tipos

† Un paso de parámetros es válido si el tipo del parámetro real es compatible con el tipo del

parámetro formal:

public double indiceRentabilidad(Deposito deposito) {

return deposito.getIntereses()/deposito.getCapital(); }

DepositoPenalizable penalizable =

(46)

Política pesimista de tipos

† El compilador rechazaría los siguientes casos:

Deposito deposito;

DepositoPenalizable penalizable = new …; deposito = penalizable;

penalizable = deposito; // Error de compilación

Deposito deposito;

DepositoPenalizable penalizable = new …; deposito = penalizable;

(47)

Ligadura dinámica

† ¿Qué versión del método getCapital() se ejecutaría?

Deposito deposito;

if (clientePreferente)

deposito = new DepositoGarantizado(...);

else

deposito = new DepositoPenalizable(...);

// Sólo se pueden aplicar operaciones de Depósito

(48)

Ligadura dinámica

† La versión de un método en una clase es la introducida

por la clase (redefinida) o la heredada.

† Versiones del método getCapital():

„ Versión Deposito: el método es definido en Depósito „ La clase DepositoPenalizable lo hereda.

„ Versión DepositoEstructurado: la clase

DepositoEstructurado redefine el método.

„ La clase DepositoGarantizado hereda la versión de

DepositoEstructurado.

† Se ejecuta la versión asociada al tipo dinámico del

(49)

Ligadura dinámica

public double posicionGlobal(Deposito[] depositos) {

double posicion = 0;

for (Deposito deposito : depositos) {

posicion += deposito.getCapital(); }

return posicion; }

(50)

Ligadura dinámica

† El método getCapital() aplicado sobre una referencia de tipo estático Deposito puede adoptar dos versiones.

† La misma llamada a un método puede tener distintas interpretaciones.

† Para que funcione la ligadura dinámica es

necesario que la variable sobre la que se aplica el método sea polimórfica (en Java siempre es así).

(51)

Raíz de una jerarquía

† Resulta muy útil disponer de una clase (tipo) que represente la raíz de una jerarquía de clases.

† Permite utilizar estructuras de datos

polimórficas:

† La clase raíz también permite incluir las

características comunes a toda la jerarquía.

Deposito[] depositos = new Deposito[3];

depositos[0] = new DepositoPenalizable (…); depositos[1] = new DepositoEstructurado (…); depositos[2] = new DepositoGarantizado (…);

(52)

Clase Object

† El lenguaje Java define la clase Object como raíz de la jerarquía de todas las clases del lenguaje. † Todas las clases heredan directa o

indirectamente de la clase Object:

„ Si una clase no hereda de ninguna otra, el compilador

añade extends Object a su declaración.

† Una variable de tipo Object puede apuntar a cualquier tipo del lenguaje:

„ Objetos creados a partir de clases.

„ Tipos primitivos asignados como objetos envoltorio

(autoboxing).

Object obj = 3;

(53)

Métodos clase Object

† En Java, la clase Object incluye las

características comunes a todos los objetos:

„ public boolean equals(Object obj) Î Igualdad de objetos

„ protected Object clone() Î Clonación de objetos

„ public String toString()

Î Representación textual de un objeto

„ public Class getClass()

Î Clase a partir de la que ha sido instanciado un objeto.

„ public int hashCode()

(54)

Copia de objetos

† La asignación de referencias (=) copia el oid del objeto y

no la estructura de datos.

† Para obtener una copia de un objeto hay que aplicar el

método clone.

† El método clone está implementado en la clase Object

(es heredado) pero no es aplicable por otras clases (visibilidad protected).

† La clase debe redefinir el método clone para aumentar

la visibilidad y crear una copia que se adapte a sus necesidades.

† La versión de la clase Object (super.clone())

(55)

Tipos de copia

† Tipos de copia:

„ Copia superficial: los campos de la copia son

exactamente iguales a los del objeto receptor.

„ Copia profunda: los campos primitivos de la copia son

iguales y las referencias a objetos son copias profundas.

(56)

Copia superficial de Cuenta

† Copia superficial: „ Aliasing incorrecto al compartir las últimas operaciones. „ No deberían tener el mismo código J. Gomez 87654321 nombre dni Objeto Persona -5000 10000 123456 100000 titular codigo saldo ultOper cuenta 123456 100000 titular codigo saldo ultOper copia

(57)

Copia profunda de Cuenta

† Copia profunda:

„ No tiene sentido duplicar el objeto cliente y que tengan

el mismo código. J. Gomez 87654321 -5000 10000 123456 100000 titular codigo saldo ultOper cuenta 123456 100000 titular codigo saldo ultOper copia -5000 10000 J. Gomez 87654321

(58)

Copia correcta de Cuenta

† Copia adaptada: cumple los requisitos de la aplicación J. Gomez 87654321 -5000 10000 123456 100000 titular codigo saldo ultOper cuenta 326457 100000 titular codigo saldo ultOper copia -5000 10000

(59)

Igualdad de objetos

† El operador == se utiliza para consultar la

identidad de referencias Î dos referencias son

idénticas si contienen el mismo oid.

† Identidad entre referencias:

„ a == c; // True

„ a == b; // False

† El método equals permite implementar la igualdad de objetos. b a c d “uno” 23 “uno” 23 “dos” 23

(60)

Método equals

† La implementación en la clase Object consiste en comprobar la identidad de la instancia actual y el parámetro.

† Por tanto: a.equals(b); // False

† Es necesario redefinir el método equals en las clases donde necesitemos la operación de

igualdad.

† Sin embargo, hay que elegir la semántica de copia más adecuada para la clase.

(61)

Tipos de igualdad

† Tipos de igualdad:

„ Superficial: los campos primitivos de los dos objetos

son iguales y las referencias a objetos idénticas (comparten los mismos objetos).

„ Profunda: los campos primitivos son iguales y las

referencias son iguales (equals) en profundidad.

„ Adaptada: adaptada a las necesidades de la

aplicación.

(62)

Métodos Object en Seminario 2

† En el seminario 2 se incluye:

„ Patrón para redefinir el método equals.

„ Redefinición del método clone utilizando la copia

superficial facilitada por la clase Object.

„ Patrón para la definición del método toString. „ Consejos para la definición de los tres métodos en

(63)

Código reutilizable

† Aplicación del método liquidar() sobre un objeto DepositoPenalizable:

„ Hereda la versión de Deposito:

„ El mensaje getCapital() aplicado sobre la instancia

actual utiliza la versión heredada de Deposito.

public double liquidar() {

return getCapital() + getIntereses(); }

(64)

Código reutilizable

† El mensaje getIntereses() aplicado sobre la instancia actual utiliza la versión redefinida en la clase.

† A su vez, la redefinición del método llama a la versión de la clase padre (Deposito).

@Override

public double getIntereses() {

if (penalizado)

return super.getIntereses() / 2;

else return super.getIntereses(); }

(65)

Código reutilizable

† La aplicación del método liquidar() sobre un objeto DepositoEstructurado:

„ Utiliza la versión heredada de Depósito.

„ El método getCapital() es redefinido en la clase y

utiliza la versión propia.

„ El método getIntereses() es redefinido en la clase

y utiliza la versión propia.

„ Ambas redefiniciones llaman a las versiones

(66)

Código reutilizable

† El método liquidar() es genérico ya que tiene diferentes interpretaciones en tiempo de ejecución según el tipo de depósito al que sea aplicado.

† Se cumple el requisito de reutilización:

variación en la implementación y representación.

(67)

Caso de estudio

† Motivación: ¿Cómo podemos establecer el tipo de interés variable sobre todos los depósitos

estructurados?

† En la aplicación almacenamos todos los depósitos en un array:

„ Deposito[] depositos;

† Una posición del array puede apuntar a cualquier tipo de depósito (estructura de datos

polimórfica).

† Sobre una referencia de tipo estático Deposito sólo puedo aplicar métodos de esa clase.

(68)

Casting

† El compilador permite hacer un casting de una variable

polimórfica a uno de los posibles tipos dinámicos de la variable.

† Sería posible hacer un casting al tipo

DepositoEstructurado que tiene el método para

establecer el tipo de interés variable:

public void setTipoInteresVariable(double tipo,

Deposito[] depositos) {

for (Deposito d : depositos) {

DepositoEstructurado de = (DepositoEstructurado) d; de.setTipoInteresVariable(tipo);

} }

(69)

Operador instanceof

† Problema:

„ Todos los depósitos no son estructurados.

„ El casting se resuelve en tiempo de ejecución y si es

incorrecto aborta el programa.

† Solución: operador instanceof

„ Permite consultar en tiempo de ejecución si el tipo

dinámico de una variable es compatible con un tipo.

„ Se entiende por tipo compatible el tipo de la consulta

o cualquiera de los subtipos.

† No es lo mismo preguntar por compatibilidad de tipos que por la clase del objeto: getClass().

(70)

Operador instanceof

public void setTipoInteresVariable(double tipo,

Deposito[] depositos) {

for (Deposito d : depositos) {

if (d instanceof DepositoEstructurado) { DepositoEstructurado de = (DepositoEstructurado) d; de.setTipoInteresVariable(tipo); } } }

(71)

Clases abstractas

† Motivación:

„ En la aplicación bancaria observamos que tanto las

cuentas como los depósitos tienen dos características en común: tienen un titular y pagan impuestos.

† Identificamos el concepto Producto Financiero y

definimos una clase que sea padre de Deposito y

Cuenta (Generalización).

† Se realiza la refactorización del código de las clases

Deposito y Cuenta para subir la funcionalidad relativa al

titular de la cuenta a la nueva clase.

† Además se introduce el método getImpuestos() que

calcula los impuestos como el 18% del beneficio del producto.

(72)

Jerarquía de herencia

Deposito DepositoEstructurado DepositoGarantizado DepositoPenalizable Cuenta ProductoFinanciero

(73)

Clase ProductoFinanciero

public class ProductoFinanciero {

private Cliente titular;

public ProductoFinanciero(Cliente titular) {

this.titular = titular; }

public Cliente getTitular() { return titular; }

public double getImpuestos() {

return getBeneficio() * 0.18; }

(74)

Clases abstractas

† ¿Cómo se calcula el beneficio de un producto financiero?

† ¿Tiene sentido incluir una implementación por defecto que retorne cero?

† El método getBeneficio() no puede ser

implementado en la clase ProductoFinanciero.

† El método getBeneficio() es abstracto. ÎEs responsabilidad de las subclases

implementarlo adecuadamente.

† La clase ProductoFinanciero es abstracta, ya

(75)

Clase ProductoFinanciero

public abstract class ProductoFinanciero {

private Cliente titular;

public ProductoFinanciero(Cliente titular) {

this.titular = titular; }

public Cliente getTitular() { return titular; }

public double getImpuestos() {

return getBeneficio() * 0.18; }

public abstract double getBeneficio(); }

(76)

Clases abstractas

† Una clase abstracta define un tipo, como cualquier otra clase.

† Sin embargo, no se pueden construir objetos de una clase abstracta.

† Los constructores sólo tienen sentido para ser utilizados en las subclases.

† Justificación de una clase abstracta:

„ Declara o hereda métodos abstractos.

„ Representa un concepto abstracto para el que no

tiene sentido crear objetos. ¿Un objeto producto

(77)

Clases parcialmente abstractas

† Clases que tienen métodos abstractos y

efectivos (implementados)

† Ejemplo: Producto Financiero.

† Los métodos efectivos pueden hacer uso de los métodos abstractos (getImpuestos()).

Î Método plantilla

† Importante mecanismo para definir código

genérico reutilizable.

† Definen comportamiento abstracto común a todos los descendientes.

(78)

Limitaciones de la herencia

† La compatibilidad de tipos que proporciona la herencia resulta útil: métodos genéricos,

variables y estructuras polimórficas, etc. † Los tipos que representa una clase son:

„ El tipo que define la clase.

„ El tipo de cada uno de sus ascendientes.

† La herencia simple limita el número de tipos que pueden representar los objetos de una clase.

(79)

Limitaciones de la herencia

† Clase totalmente abstracta: sólo declara

métodos abstractos.

† Clases y naturaleza módulo/tipo:

„ Módulo: código que implementa un TAD. „ Tipo: define un nuevo tipo de datos.

† Una clase totalmente abstracta no tiene código (se pierde la naturaleza de módulo).

(80)

Limitaciones de la herencia

† Una clase que hereda de otra totalmente abstracta:

„ No reutiliza código, sólo la exigencia de implementar

métodos (implementar un tipo)

† ¿Podríamos heredar de más de una clase totalmente abstracta?

(81)

Interfaces

† Construcción proporcionada por Java para la

definición tipos (sin implementación).

† Equivale a una clase totalmente abstracta y aporta ventajas.

† Se dice que una clase implementa una interfaz. † El número de interfaces que puede implementar

una clase no está limitado (implementación

múltiple de interfaces).

Î Los tipos que representan los objetos de una clase pueden ser ampliados con la

(82)

Interfaz Amortizable

† Ejemplo: un deposito es amortizable si permite rescatar parte del capital antes del vencimiento. † Se define este tipo de depósitos como una

interfaz.

public interface Amortizable {

boolean amortizar(double cantidad); }

(83)

Interfaz Amortizable

† Por defecto, en una interfaz la visibilidad de los

métodos es pública.

† Las interfaces pueden incorporar declaraciones (simplificadas) de constantes:

public interface Amortizable {

double LIMITE = 10000.0;

boolean amortizar(double cantidad); }

(84)

Implementación de una interfaz

† Ejemplo: sólo los depósitos garantizados y los penalizables pueden ser amortizables.

† Las dos clases implementan la interfaz:

public class DepositoPenalizable extends Deposito

implements Amortizable {

@Override

public boolean amortizar(double cantidad) {

if (cantidad > capital)

return false;

capital = capital - cantidad;

return true; }

(85)

Extensión de interfaces

† Una interfaz puede extender otras interfaces:

public interface Flexible extends Amortizable, Incrementable {

void actualizarTipoInteres(double tipo); }

(86)

Interfaces - Resumen

† Las interfaces definen tipos que deben ser

implementados por clases.

„ Amortizable deposito = new DepositoPenalizable(…);

† Si una clase no implementa algún método de una interfaz debe declararse abstracta.

† Una interfaz define un tipo, pero no se pueden

construir objetos de una interfaz:

„ Amortizable deposito = new Amortizable(); // Error

† Las interfaces resuelven la limitación de tipos

(87)

Dependencias de código

† En un equipo de desarrollo puede resultar

complejo gestionar la dependencias de código: „ Programador A depende del código del

programador B y viceversa.

† Las interfaces proporcionan un mecanismo para la definición de contratos (tipos) entre distintas partes de la aplicación:

„ Interfaz gráfica – Aplicación

„ Aplicación – Persistencia de datos.

† Cada parte puede ser compilada y probada

(88)

Genericidad

† Genericidad: facilidad de un lenguaje de programación para definir clases

parametrizadas con tipos de datos.

† Dentro de una clase genérica, sobre las variables de tipo genérico sólo es posible aplicar:

„ Asignación (=) e identidad (== o !=). „ Métodos de la clase Object.

† Sería posible aplicar más operaciones si aplicamos genericidad restringida:

„ En la declaración del tipo genérico indicamos que

debe ser compatible con un tipo de la aplicación

(89)

Genericidad restringida

public class Contenedor<T extends Deposito> {

private T contenido;

public void setContenido(T contenido) {

this.contenido = contenido; }

public T getContenido() {

return contenido; }

(90)

Genericidad restringida

† ¿Sería equivalente a la declaración anterior? public class Contenedor {

private Deposito contenido;

public void setContenido(Deposito contenido) {

this.contenido = contenido; }

public Deposito getContenido() {

return contenido; }

(91)

Genericidad restringida

† Con genericidad restringida podemos declarar la siguiente variable:

„ Contenedor<DepositoGarantizado> seguro = …;

„ Es posible especificar el tipo de depósito del

contenedor, compatible con Deposito.

„ En el ejemplo sólo podrían introducirse depósitos

garantizados.

† En cambio, con la segunda declaración no podemos restringir el tipo de depósito.

(92)

Genericidad restringida

† Aplicando genericidad restringida:

„ Limitamos el tipo al que se puede parametrizar la clase

genérica.

„ Sobre las variables genéricas podemos aplicar

métodos del tipo al que se ha restringido.

† Ejemplo: limitar los depósitos cuyo titular sea preferente.

public void setContenido(T contenido) {

if (contenido.getTitular() instanceof ClientePreferente)

this.contenido = contenido;

else

this.contenido = null; }

(93)

Genericidad restringida

† Una clase genérica puede estar restringida por

varios tipos:

† Por tanto, las operaciones disponibles son las de todos los tipos.

(94)

Genericidad y sistema de tipos

Deposito DepositoEstructado Contenedor<Deposito> Contenedor<DepositoEstructurado> No hay relación Contenedor<Deposito> cDeposito; Contenedor<DepositoEstructurado> cEstructurado;

(95)

Genericidad y sistema de tipos

† El compilador trata de asegurar que sobre un contenedor de depósitos estructurados no se incluyan depósitos básicos.

† Sin embargo, existe un agujero en el sistema

de tipos de Java:

† Cuando se declara una variable de un tipo

genérico sin parametrizar se asigna el tipo puro (raw) que corresponde a Object.

Contenedor contenedor;

contenedor = cDeposito; // compila

contenedor = cEstructurado; // compila

(96)

Genericidad y sistema de tipos

† Utilizando el tipo puro podemos saltar la seguridad de tipos:

Contenedor<DepositoEstructurado> cEstructurado = new ...; Contenedor<Deposito> cDeposito;

Contenedor contenedor;

contenedor = cEstructurado;

(97)

Genericidad y herencia

† Una clase puede heredar de una clase genérica. † La nueva clase tiene las opciones:

„ Mantener la genericidad de la clase padre.

„ Restringir la genericidad.

„ No ser genérica y especificar un tipo concreto.

public class CajaSeguridad<T> extends Contenedor<T>

public class CajaSeguridad<T extends Valorable>

extends Contenedor<T>

(98)

Genericidad y tipos comodín

† ¿Cómo podemos pasar una lista de depósitos estructurados?

public double posicionGlobal(List<Deposito> depositos) {

double posicion = 0;

for (Deposito deposito : depositos) {

posicion += deposito.getCapital(); }

return posicion; }

(99)

Genericidad y tipos comodín

† Se utiliza un comodín, que significa: permite cualquier lista genérica instanciada a la clase Depósito o a una subclase.

† Si pasamos como parámetro un objeto

List<DepositoEstructurado>, éste será el tipo

reconocido dentro del método.

† No hay riesgo de inserciones ilegales dentro de la colección.

public double posicionGlobal(

(100)

Genericidad y métodos

† La genericidad también puede aplicarse en la definición de métodos, especialmente de clase. † Ejemplo: métodos de la clase Collections

† Al hacer la llamada el compilador infiere el tipo T y comprueba que se cumplan las restricciones:

„ El tipo de la colección debe ser del tipo inferido o un

subtipo.

„ El tipo del comparador debe ser del tipo inferido o un

supertipo.

static <T> T max (Collection<? extends T> coll, Comparator<? super T> comp) {… }

(101)

Genericidad y máquina virtual

† La máquina virtual no maneja objetos de tipo

genérico.

† En tiempo de ejecución todo tipo genérico se transforma a un tipo puro.

† Con el operador instanceof sólo podemos preguntar por el nombre de la clase.

// No compila

if (contenedor instanceof Contenedor<Deposito>) { … }

// Sí compila

(102)

Código reutilizable

Coleccion comenzar fin avanzar item buscar(x) Array comenzar fin avanzar FicheroSec comenzar fin avanzar ListaEnlazada comenzar fin avanzar boolean buscar (T x){ comenzar();

while (!x.equals(item() && !fin()){

avanzar();

}

return x.equals(item()) ; }

Esquema general o patrón

(103)

Código reutilizable

public abstract class Coleccion<T> {

public boolean buscar(T x) { comenzar();

while (!x.equals(item()) && !fin()) { avanzar();

}

return x.equals(item()); }

public abstract void comenzar();

public abstract boolean fin();

public abstract T item();

public abstract void avanzar(); }

(104)

Código reutilizable

† La implementación del método buscar() cumple

los requisitos de reutilización del tema 1:

Array<Integer> enteros = new Array<Integer>(); ...

enteros.buscar(10);

FicheroSec<String> cadenas = FicheroSec<String>(); ...

(105)

Caso de estudio

† Motivación:

„ Método de búsqueda con una condición de búsqueda.

Coleccion comenzar fin avanzar Item condicion buscar boolean buscar () {

boolean encontrado = false; comenzar();

while (!fin() && !encontrado){ if (condicion(item()) encontrado = true; else avanzar(); } return encontrado; }

(106)

Caso de estudio

† Problemas de la solución:

„ La condición de búsqueda debe ser implementada por

los subtipos: Array, ListaEnlazada, etc.

„ ¿Cómo se implementa la condición en Array? La

condición depende de la aplicación y no del tipo de colección.

† Alternativa:

„ Establecer la condición como parámetro (acción) „ Problema: en Java no es posible definir punteros a

(107)

Acciones

† Solución basada en interfaces:

„ Se define una interfaz con el método test para

comprobar la condición.

public interface ICondicion<T> {

boolean test(T elemento); }

(108)

Acciones

† Se establece la condición como parámetro. public boolean buscar(ICondicion<T> condicion) {

boolean encontrado = false; comenzar();

while (!fin() && !encontrado){

if (condicion.test(item())) encontrado = true; else avanzar(); } return encontrado; }

(109)

Acciones

† Se crea una clase por cada condición.

public class CondicionCapital implements ICondicion<Deposito>{

@Override

public boolean test(Deposito elemento) {

return elemento.getCapital() > 100000 }

(110)

Acciones

† ¿Tiene sentido crear una clase por cada condición?

† Solución basada en clases anónimas.

Coleccion<Deposito> depositos = …;

depositos.buscar(new ICondicion<Deposito>() {

@Override

public boolean test(Deposito elemento) {

return elemento.getCapital() > 100000; }

(111)

Clases anónimas

† Las clases anónimas permiten la creación de

una clase y la construcción de un objeto en línea.

† Ejemplo:

„ La clase anónima implementa el interfaz

ICondicion<Deposito>

„ Se construye un objeto de esa clase.

(112)

Clases anónimas

† Dentro del código de una clase anónima se puede hacer

uso de variables locales o parámetros si se declaran

finales:

Coleccion<Deposito> depositos;

public boolean buscarUmbral (final double umbral){

return depositos.buscar(new ICondicion<Deposito>(){

@Override

public boolean test (Deposito elemento){

return elemento.getCapital() > umbral; }

(113)

Iteradores

† Iterar significa ejecutar una cierta acción sobre todos los

elementos de una estructura de datos o sobre aquellos que cumplan una condición.

† El cliente que realiza la iteracción no debe conocer las

particularidades de la estructura de datos (abstracción

por iteración).

public double posicionGlobal(List<Deposito> depositos) {

double posicion = 0;

for (Deposito deposito : depositos) {

posicion += deposito.getCapital(); }

return posicion; }

(114)

Iteradores

† Java proporciona la interfaz Iterable<T> que

debe ser implementada por aquellas clases sobre las que se pueda iterar:

† A los objetos iterables se les exige que creen objetos iterador (Iterator) para realizar la iteración.

public interface Iterable<T> {

Iterator<T> iterator(); }

(115)

Iteradores

† Interfaz Iterator<T>:

„ hasNext(): indica si quedan elementos en la iteración.

„ next(): devuelve el siguiente elemento de la iteración.

„ remove(): elimina el último elemento devuelto por el

iterador.

public interface Iterator<E> {

boolean hasNext();

E next();

void remove(); }

(116)

Iteradores

† Las colecciones de Java son iterables.

† El recorrido for each sólo es aplicable sobre objetos iterables

Æ Evita tener que manejar explícitamente el

objeto iterador.

† La implementación de un iterador requiere crear una clase responsable de llevar el estado de la iteración.

(117)

Clases internas

† Una clase interna es una clase que se define

dentro de otra clase.

† Los objetos de esta clase están ligados al

objeto de la clase contenedora donde se crean.

† La clase interna tiene visibilidad sobre las declaraciones de la clase contenedora.

† Los objetos internos pueden acceder a los

atributos y métodos del objeto contenedor como si fueran propios.

† Los objetos internos se crean dentro de métodos de instancia o constructores de la clase que los contiene.

(118)

Iterador y clases internas

public class Lista<T> implements Iterable<T>{

public Iterator<T> iterator() {

return new Itr<T>();

}

private class Itr<T> implements Iterator<T> {

int cursor = 0;

public boolean hasNext() {

return cursor != size();

}

public T next() {

return get(cursor++);

}

public void remove() { …}

(119)

Colecciones en Java

† Las colecciones en Java son un ejemplo destacado de implementación de código

reutilizable utilizando un lenguaje orientado a

objetos.

† Todas las colecciones son genéricas e

iterables.

† Los tipos abstractos de datos se definen como

interfaces.

† Se implementan clases abstractas que permiten factorizar el comportamiento común a varias

implementaciones.

† Un mismo TAD puede ser implementado por varias clases Æ List: LinkedList,

(120)

Colecciones en Java

Collection ListIterator Iterator Map AbstractMap SortedMap AbstractSequentialList TreeMap HashMap SortedSet AbstractSet AbstractList AbstractCollection Set List HashSet TreeSet ArrayList extends implements class interface devuelve devuelve devuelve

(121)

Herencia múltiple

† ¿Qué proporciona la herencia?

„ Posibilidad de reutilizar el código de otra clase

(perspectiva módulo).

„ Hacer que el tipo de la clase sea compatible con el de

otra clase (perspectiva tipo).

† En Java la herencia es simple.

† La limitación de tipos impuesta por la herencia es superada con el uso de interfaces.

† Sin embargo, sólo es posible reutilizar código de una clase.

(122)

Herencia múltiple

† Motivación:

„ Cuenta Remunerada: es una cuenta bancaria que

también ofrece rentabilidad como un depósito.

„ La clase CuentaRemunerada debería heredar de

Cuenta y de Deposito.

Deposito

CuentaRemunerada

(123)

Herencia múltiple

† En Java sólo podemos heredar de una clase:

„ Elegimos la clase Cuenta como clase padre.

† ¿Cómo conseguimos que CuentaRemunerada sea compatible con el tipo Deposito?

„ Definimos la interfaz IDeposito y hacemos que la

clase Deposito implemente la interfaz.

† CuentaRemunerada también implementa la

(124)

Herencia múltiple

public interface IDeposito {

double liquidar(); double getIntereses(); double getCapital(); int getPlazoDias(); double getTipoInteres(); Cliente getTitular(); }

public class Deposito extends ProductoFinanciero

(125)

Herencia múltiple

† CuentaRemunerada

es compatible con el tipo IDeposito

definido por la interfaz.

† ¿Cómo podemos reutilizar la implementación de la clase Deposito? IDeposito Barco Cuenta CuentaRemunerada Deposito «implements» «extends»

(126)

Herencia múltiple – Solución 1

† Solución 1:

„ Si no es necesario extender o modificar la

definición de la clase Deposito, establecemos una relación de clientela.

IDeposito Barco Cuenta CuentaRemunerada Deposito «implements» «extends» depositoImpl

(127)

Solución 1: relación de clientela

public class CuentaRemunerada extends Cuenta

implements IDeposito {

private Deposito depositoImpl;

public CuentaRemunerada(Cliente titular,

int saldoInicial, double tipoInteres) {

super(titular, saldoInicial);

// Liquidacion mensual de intereses depositoImpl =

new Deposito(titular,saldoInicial,30,tipoInteres);

(128)

Solución 1: relación de clientela

† La clase CuentaRemunerada delega la

implementación de los métodos de la interfaz IDeposito en la clase Deposito

// Implementación IDeposito @Override

public double getCapital() {

return depositoImpl.getCapital(); }

@Override

public double getIntereses() {

return depositoImpl.getIntereses(); }

(129)

Solución 2: clases internas

† Solución 2: clases internas

„ Si necesitamos reutilizar la implementación de la clase Deposito pero extendiendo la definición original.

† Definimos una clase interna que herede de

Deposito:

„ La clase interna puede redefinir métodos de Deposito.

„ La clase interna puede aplicar métodos de la clase

(130)

Solución 2: clases internas

public class CuentaRemunerada extends Cuenta

implements IDeposito {

private double saldoMedioUltimoMes() { … }

private class DepositoImpl extends Deposito {

DepositoImpl(Persona titular, double capital,

int plazoDias, double tipoInteres) {

super(titular, capital, plazoDias, tipoInteres); }

@Override

public double getCapital() {

return saldoMedioUltimoMes(); }

(131)

Solución 2: clases internas

public class CuentaRemunerada extends Cuenta

implements IDeposito {

private Deposito depositoImpl;

public CuentaRemunerada(Cliente titular,

int saldoInicial, double tipoInteres) {

super(titular, saldoInicial);

// Liquidacion mensual de intereses depositoImpl =

new DepositoImpl(titular,saldoInicial,30,tipoInteres); }

(132)

Solución 2: clases internas

† La clase CuentaRemunerada delega la

implementación de los métodos de la interfaz

IDeposito en la clase Deposito (igual solución 1)

// Implementación IDeposito @Override

public double getCapital() {

return depositoImpl.getCapital(); }

@Override

public double getIntereses() {

return depositoImpl.getIntereses(); }

(133)

Consejos uso de la herencia

† Los atributos y métodos comunes deben situarse en clases altas de la jerarquía (generalización). † Evita el uso de atributos protegidos.

Compromete el principio de ocultación de la información.

† Aplica herencia si entre dos clases existe la relación es-un.

† No debe utilizarse herencia salvo que todos los métodos heredados tengan sentido.

† En la redefinición no hay que cambiar la

semántica del método.

† Aplica polimorfismo y ligadura dinámica para evitar análisis de casos.

(134)

Seminarios 2 y 3

† Este tema se completa con los seminarios 2 y 3. † El seminario 2 trata:

„ Conceptos básicos de la herencia. „ Polimorfismo.

„ Herencia y sistema de tipos. „ Ligadura dinámica.

„ Métodos de la clase Object: igualdad, copia, etc. „ Pruebas y herencia

† El seminario 3 trata:

„ Clases abstractas. „ Genericidad.

Figure

Actualización...

Referencias

Actualización...

Related subjects :