Tema 3 Herencia en Java Parte 2. Programación Orientada a Objetos Curso 2015/2016

Texto completo

(1)

Tema 3 – Herencia en Java – Parte 2

Programación Orientada a Objetos

Curso 2015/2016

(2)

Contenido

Restringir la herencia.

Visibilidad protegida.

Clases abstractas.

Clase

Object

.

Autoboxing.

Igualdad de objetos.

Copia de objetos.

(3)

Restringir la herencia

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

La redefinición incorrecta del algoritmo del método

situar(Punto) de la Burbuja 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.

(4)
(5)

Restringir la herencia

public final class String … {

}

(6)

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 region de Burbuja. La burbuja creciente tiene que modificar la región.

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

(7)

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

(8)

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.

En Burbuja >> Burbuja getCopia() {…}

En BurbujaLimitada >> BurbujaLimitada getCopia() {…}

Es posible modificar la declaración de las excepciones (throws), reduciendo la lista de excepciones

(9)

Excepciones comprobadas y Herencia

No se puede incluir una nueva excepción comprobada que no lance el método de la clase padre.

No se puede incluir una excepción comprobada que sea más general que la que se hereda.

Ejemplo: en la clase padre el método lanza

FileNotFoundException y la redefinición IOException que es un supertipo.

Es posible indicar una excepción más específica que la que se hereda:

Ejemplo: en la clase padre el método lanza IOException y

(10)

Clases abstractas

Motivación:

Tanto los círculos como los rectángulos tiene perímetro. Ambos se pueden desplazar.

Pueden tener en común una nueva propiedad color.

Identificamos el concepto Figura geométrica y definimos una clase que sea padre de Circulo y Rectangulo

(Generalización).

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

Circulo y Rectangulo para subir la funcionalidad común a la nueva clase.

(11)
(12)

Clases abstractas

¿Cómo se calcula el perímetro de una figura geométrica? ¿Tiene sentido incluir una implementación por defecto que

retorne cero?

El método getPerimetro() no puede ser implementado en la clase Figura.

El método getPerimetro() es abstracto.

Es responsabilidad de las subclases implementarlo adecuadamente.

La clase Figura es abstracta, ya que tiene un método abstracto.

(13)

Clase

Figura

public abstract class Figura {

private Color color;

protected Figura(Color color){

this.color = color; }

public Color getColor(){

return color; }

public abstract double getPerimetro();

(14)

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

y/o representa un concepto abstracto para el que no tiene

(15)

Clases parcialmente abstractas

Clases que tienen métodos abstractos y efectivos

(implementados)

Ejemplo: Figura puede implementar el algoritmo de desplazamiento.

Método plantilla: método efectivo que hace uso de métodos abstractos.

Ejemplo: desplazar(int cantidad, Direccion dir) utiliza desplazar(int,int)abstracto.

Importante mecanismo para definir código reutilizable. Definen comportamiento común a todos los descendientes.

(16)

Método plantilla en

Figura

public void desplazar(int cantidad, Direccion dir) {

int incX = 0;

int incY = 0;

switch (dir) {

case ARRIBA: incY = cantidad; break; case ABAJO: incY = - cantidad; break; case DERECHA: incX = cantidad; break;

case IZQUIERDA: incX = - cantidad; break;

}

desplazar(incX, incY); }

(17)
(18)

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.

(19)

Clase

Object

y Autoboxing

El autoboxing es un mecanismo automático encargado de

convertir objetos en tipos primitivos y viceversa. De este modo, una referencia de tipo Object puede

apuntar a cualquier tipo de datos.

Conversión de tipos primitivos a objetos:

Java construye objetos envoltorio donde almacenar los

valores primitivos.

Para cada tipo primitivo hay una clase envoltorio: Integer

para int, Double para double, etc.

La conversión de un objeto en un tipo primitivo requiere hacer el casting al tipo envoltorio.

Object obj = 3;

(20)

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 final Class getClass()

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

public int hashCode()

(21)

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

(22)

Método

equals

La implementación en la clase Object consiste en

comprobar la identidad del objeto receptor 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 igualdad más adecuada para la clase.

public boolean equals(Object obj) {

return this == obj; }

(23)

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.

(24)

Método

equals

en

Cuenta

@Override

public boolean equals(Object obj) {

if (obj == null) return false;

if (obj == this) return true;

if (obj.getClass() != this.getClass())

return false;

Cuenta otraCta = (Cuenta)obj;

return (this.codigo == otraCta.codigo) && (this.titular == otraCta.titular) && (this.saldo == otraCta.saldo) &&

(this.estado == otraCta.estado) &&

(Arrays.equals(this.ultimasOperaciones,

otraCta.ultimasOperaciones); }

(25)

Método

equals

en

Cuenta

Explicación de la implementación:

1. Caso trivial: el parámetro es la referencia nula.

2. Caso trivial: dos objetos con el mismo oid siempre serán

iguales.

3. Comprueba que el tipo dinámico de los objetos sea el

mismo (método getClass).

Ejemplo: una burbuja sensible nunca será igual que una burbuja

débil.

4. Realiza el casting del parámetro (siempre de tipo Object)

para poder comparar sus atributos

La consulta del tipo dinámico garantiza el casting correcto.

5. Comparación de los atributos

Nota: un objeto tiene acceso a los atributos de otro de su misma

(26)

Método

equals

en herencia

El método equals() heredado en una subclase es correcto si:

No aporta nuevos atributos.

Los atributos que aporta no se tienen en cuenta en la igualdad.

Si fuera necesario redefinir, debe reutilizarse la versión heredada:

public boolean equals(Object obj) {

if (super.equals(obj) == false) return false; CuentaRemunerada otroObj = (CuentaRemunerada)obj;

return …; // compara atributos propios

(27)

Método

hashCode

Devuelve un número que representa el código de dispersión (código hash) de un objeto.

Importante si los objetos se almacenan en colecciones cuya implementación se basa en una tabla de dispersión (HashMap y HashSet, se estudian en tema 5).

Importante: la implementación del hashCode() tiene que ser consistente con la implementación del equals()

Si o1.equals(o2) es true entonces o1.hashCode() == o2.hashCode() Lo contrario no tiene por qué ser cierto.

(28)

Método

hashCode

La implementación por defecto del método hashCode() deriva el código de dispersión de la dirección de memoria del objeto.

Importante: si en una clase se redefine el método equals() también hay que redefinir el método

hashCode() para que las implementaciones sean consistentes.

La implementación del método hashCode() se calcula como una combinación del código hash de cada uno de los atributos.

En el caso de atributos de tipo primitivo se utilizan las clases

envolventes, salvo para el tipo int.

(29)

Método

hashCode

en

Cuenta

@Override

public int hashCode() {

int primo = 31;

int result = 1;

result = primo * result + codigo;

result = primo * result + ((titular == null) ? 0 : titular.hashCode());

result = primo * result + new Double(saldo).hashCode(); result = primo * result + ((estado == null) ? 0 :

estado.hashCode());

result = primo * result + Arrays.hashCode(ultimasOperaciones);

return result; }

(30)

Método

hashCode

en herencia

public int hashCode() {

int primo = 31;

int result = super.hashCode();

// añadir atributos utilizados en equals() redefinido

result = primo * result + … ;

return result; }

El método hashCode()se redefine en una subclase si se ha redefinido el método equals()

consistencia de las implementaciones.

(31)

Método

toString

en

Cuenta

@Override

public String toString() {

return getClass().getName() + "[codigo = " + codigo + ", titular = "+ titular + ", saldo = "+ saldo + ",ultimasOperaciones = " + Arrays.toString(ultimasOperaciones) + "]"; }

(32)

Método

toString

en herencia

El método toString()debe ser redefinido en una subclase si añade nuevos atributos.

En caso de redefinición, reutilizar la versión heredada:

La llamada getClass().getName() siempre mostrará correctamente el nombre de la clase:

getClass() retorna el tipo de la instancia actual.

public String toString() {

// añade a la cadena atributos propios

return super.toString() + "[ … ]";

(33)

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 implementación de clone() en la clase Object construye una copia superficial de la instancia actual.

(34)

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.

(35)

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

(36)

Copia profunda de

Cuenta

Copia profunda

:

No tiene sentido duplicar el objeto persona y que tengan el mismo código.

J. Gomez -5000 10000 123456 100000 titular codigo saldo ultOper cuenta 123456 100000 titular codigo saldo ultOper copia -5000 10000 J. Gomez

(37)

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

(38)

Método

clone

en

Cuenta

public class Cuenta implements Cloneable{

public Cuenta clone(){ Cuenta copia = null;

try{

copia = (Cuenta)super.clone(); copia.codigo = ++ultimoCodigo; copia.ultimasOperaciones =

Arrays.copyOf(ultimasOperaciones, ultimasOperaciones.length);

}catch (CloneNotSupportedException e){ // No sucede si la clase es Cloneable

System.err.println("La clase no implementa " +

"la interfaz Cloneable");

}

return copia; }

(39)

Método

clone

– Recomendaciones

Aumentar la visibilidad del método clone() para pasar de protected a public

Aplicar la regla covariante para establecer el tipo de la clase (el tipo de la copia).

Realizar una copia basada en la implementación de clone() en Object copia superficial

Tratar el error de copia, sin hacer nada (las excepciones se

estudian en el tema 4).

No se produce ningún error de copia si la clase implementa

la interfaz vacía Cloneable.

Adaptar la copia superficial según las necesidades de la aplicación.

(40)

Método

clone

– Recomendaciones

Ejemplo de adaptación en la clase Cuenta:

Resuelve el aliasing incorrecto del array.

Asigna un nuevo código de cuenta a la copia.

Importante: implementar el método clone() a partir de la copia superficial hace que la implementación sea

heredable

La llamada al método clone() de Object siempre retorna

un objeto igual a la instancia actual.

Una subclase sólo necesitaría redefinir el método si los

atributos que aporta no son copiados correctamente con la copia superficial (por ejemplo, aliasing incorrecto).

(41)

Método

clone

– Recomendaciones

En herencia, aunque el método clone heredado sea correcto, conviene redefinirlo aplicando la regla

covariante y llamar a la versión del padre:

Nota: el casting siempre será correcto, ya que la copia superficial siempre garantiza que el objeto que retorna es del tipo de la instancia actual.

public class CuentaRemunerada extends Cuenta {

public CuentaRemunerada clone(){

return (CuentaRemunerada)super.clone(); }

(42)

Método

getClass()

vs operador

instanceof

El método getClass()retorna el tipo dinámico de la referencia.

En general no es recomendado su uso. En su lugar es preferible el operador instanceof para consultar la compatibilidad de tipos.

Ejemplo: la consulta con getClass() deja fuera las burbujas crecientes, que también son burbujas débiles (incorrecto).

public void simulador(Burbuja burbuja) {

if (burbuja.getClass() == BurbujaDebil.class) {

(43)

Seminario 2

Este tema se completa con el seminario 2 (2ª parte).

Visibilidad y herencia Clase Object

Autoboxing

Métodos de la clase Object: igualdad, copia, etc.

Figure

Actualización...

Referencias

Actualización...