Tema 3 – Herencia en Java – Parte 2
Programación Orientada a Objetos
Curso 2015/2016
Contenido
•
Restringir la herencia.
•
Visibilidad protegida.
•
Clases abstractas.
•
Clase
Object
.
•
Autoboxing.
•
Igualdad de objetos.
•
Copia de objetos.
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.
Restringir la herencia
public final class String … { …
}
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
Niveles de visibilidad
En Java, los niveles de visibilidad son incrementales
private
()
paquete
protected
public
public todo el códigoprotected clase + paquete + subclases (nada) clase + paquete
Revisión redefinición de métodos
Al redefinir un método podemos cambiar suimplementació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
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
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.
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.
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(); …
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
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.
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); }
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.
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;
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()
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” 23Mé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; }
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.
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); }
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
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
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.
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.
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; }
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.
Método
toString
en
Cuenta
@Override
public String toString() {
return getClass().getName() + "[codigo = " + codigo + ", titular = "+ titular + ", saldo = "+ saldo + ",ultimasOperaciones = " + Arrays.toString(ultimasOperaciones) + "]"; }
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() + "[ … ]";
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.
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.
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 copiaCopia 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
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 10000Mé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; }
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.
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).
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(); }
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) { …
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.