Upload
others
View
19
Download
0
Embed Size (px)
Citation preview
Tema 3 – Herencia en Java – Parte 2
Programación Orientada a Objetos
Curso 2015/2016
Programación Orientada a Objetos 2Curso 2015/2016
Contenido
• Restringir la herencia.
• Visibilidad protegida.
• Clases abstractas.
• Interfaces.
• Clase Object.
• Autoboxing.
• Copia de objetos.
• Igualdad de objetos.
Programación Orientada a Objetos 3Curso 2015/2016
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.
Programación Orientada a Objetos 4Curso 2015/2016
Restringir la herencia
public class Deposito {
…
public final double liquidar() {
return getCapital() + getIntereses();
}
}
� El algoritmo del método liquidar es el mismo para todos
los depósitos, no se puede redefinir.
Programación Orientada a Objetos 5Curso 2015/2016
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.
Programación Orientada a Objetos 6Curso 2015/2016
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
private � clase
Programación Orientada a Objetos 7Curso 2015/2016
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 titulares sean de tipo PersonaPreferente,
subclase de Persona.
� En este caso, el método de consulta se redefine como
PersonaPreferente getTitular().
Programación Orientada a Objetos 8Curso 2015/2016
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 a la nueva clase.
� Además se introduce el método getImpuestos() que
calcula los impuestos como el 21% del beneficio del producto.
Programación Orientada a Objetos 9Curso 2015/2016
Jerarquía de herencia
Programación Orientada a Objetos 10Curso 2015/2016
Clase ProductoFinanciero
public class ProductoFinanciero {
private Persona titular;
public ProductoFinanciero(Persona titular) {
this.titular = titular;
}
public Persona getTitular() { return titular; }
public double getImpuestos() {
return getBeneficio() * 0.21;
}
}
Programación Orientada a Objetos 11Curso 2015/2016
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 que tiene
un método abstracto.
Programación Orientada a Objetos 12Curso 2015/2016
Clase ProductoFinanciero
public abstract class ProductoFinanciero {
private Persona titular;
public ProductoFinanciero(Persona titular) {
this.titular = titular;
}
public Persona getTitular() { return titular; }
public double getImpuestos() {
return getBeneficio() * 0.21;
}
public abstract double getBeneficio();
}
Programación Orientada a Objetos 13Curso 2015/2016
Jerarquía de herencia
Programación Orientada a Objetos 14Curso 2015/2016
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
sentido crear objetos: ¿un objeto producto financiero o figura
geométrica?
Programación Orientada a Objetos 15Curso 2015/2016
Clases parcialmente abstractas
� Clases que tienen métodos abstractos y efectivos
(implementados)
� Ejemplo: Producto Financiero.
� Método plantilla: método efectivo que hace uso de
métodos abstractos.
� Ejemplo: getImpuestos().
� Importante mecanismo para definir código reutilizable.
� Definen comportamiento común a todos los
descendientes.
Programación Orientada a Objetos 16Curso 2015/2016
Limitaciones de la herencia
� La compatibilidad de tipos que proporciona la herencia
resulta útil: variables y estructuras polimórficas, código
reutilizable, etc.
� Los tipos que representan los objetos de 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.
� Solución: interfaces
Programación Orientada a Objetos 17Curso 2015/2016
Interfaces
� Construcción proporcionada por Java para la definición de tipos (sin implementación).
� 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 implementación de interfaces.
Programación Orientada a Objetos 18Curso 2015/2016
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);}
Programación Orientada a Objetos 19Curso 2015/2016
Interfaz Amortizable
� Por defecto, en una interfaz la visibilidad de las declaraciones es pública.
� Las interfaces pueden incorporar declaraciones
(simplificadas) de constantes:
public interface Amortizable {
double LIMITE = 10000.0;
boolean amortizar(double cantidad);
}
Programación Orientada a Objetos 20Curso 2015/2016
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 {
…@Overridepublic boolean amortizar(double cantidad) {
if (cantidad > capital) return false;
capital = capital - cantidad;return true;
}}
Programación Orientada a Objetos 21Curso 2015/2016
Interfaces
Programación Orientada a Objetos 22Curso 2015/2016
Extensión de interfaces
� Una interfaz puede extender otras interfaces:
public interface Flexible extends Amortizable, Incrementable {
void actualizarTipoInteres(double tipo);
}
Programación Orientada a Objetos 23Curso 2015/2016
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 impuesta por la herencia simple.
Programación Orientada a Objetos 24Curso 2015/2016
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 gracias al autoboxing.
Programación Orientada a Objetos 25Curso 2015/2016
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: Integerpara 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;
int entero = (Integer)obj;
Programación Orientada a Objetos 26Curso 2015/2016
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()
� Código hash utilizado en las colecciones.
Programación Orientada a Objetos 27Curso 2015/2016
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.
Programación Orientada a Objetos 28Curso 2015/2016
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.
� Adaptada a las necesidades de la aplicación.
Programación Orientada a Objetos 29Curso 2015/2016
Copia superficial de Cuenta
� Copia superficial:
� Aliasingincorrecto al compartir las últimas operaciones.
� No deberían tener el mismo código
J. Gomez
87654321
nombre
dni
Objeto Persona
-500010000
123456
100000
titular
codigo
saldo
ultOper
cuenta
123456
100000
titular
codigo
saldo
ultOper
copia
Programación Orientada a Objetos 30Curso 2015/2016
Copia profunda de Cuenta
� Copia profunda:� No tiene sentido duplicar el objeto persona y que
tengan el mismo código.
J. Gomez
87654321
-500010000
123456
100000
titular
codigo
saldo
ultOper
cuenta
123456
100000
titular
codigo
saldo
ultOper
copia
-500010000
J. Gomez
87654321
Programación Orientada a Objetos 31Curso 2015/2016
Copia correcta de Cuenta
� Copia adaptada: cumple los requisitos de la aplicación
J. Gomez
87654321
-500010000
123456
100000
titular
codigo
saldo
ultOper
cuenta
326457
100000
titular
codigo
saldo
ultOper
copia
-500010000
Programación Orientada a Objetos 32Curso 2015/2016
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;
}
}
Programación Orientada a Objetos 33Curso 2015/2016
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 5).
� 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.
Programación Orientada a Objetos 34Curso 2015/2016
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).
Programación Orientada a Objetos 35Curso 2015/2016
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();
}
}
Programación Orientada a Objetos 36Curso 2015/2016
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
Programación Orientada a Objetos 37Curso 2015/2016
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;
}
Programación Orientada a Objetos 38Curso 2015/2016
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 a las necesidades de la aplicación.
Programación Orientada a Objetos 39Curso 2015/2016
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);
}
Programación Orientada a Objetos 40Curso 2015/2016
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: un depósito garantizado nunca será igual que un depósito estructurado.
� 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 mismaclase.
Programación Orientada a Objetos 41Curso 2015/2016
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
}
Programación Orientada a Objetos 42Curso 2015/2016
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 4).
� Importante: la implementación del hashCode() tiene que ser consistente con la implementación del equals()
� Si o1.equals(o2) es true entonceso1.hashCode() == o2.hashCode()
� Lo contrario no tiene por qué ser cierto.
Programación Orientada a Objetos 43Curso 2015/2016
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 clasesenvolventes, salvo para el tipo int.
� Siguiendo la implementación, la probabilidad de que objetos distintos tengan el mismo código de dispersión es muy baja.
Programación Orientada a Objetos 44Curso 2015/2016
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;
}
� Patrón de redefinición del método hashCode
Programación Orientada a Objetos 45Curso 2015/2016
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.
� La redefinición debe reutilizar la versión heredada.
Programación Orientada a Objetos 46Curso 2015/2016
Método toString en Cuenta
@Override
public String toString() {
return getClass().getName() +
"[codigo = " + codigo +
", titular = "+ titular +
", saldo = "+ saldo +
",ultimasOperaciones = " +
Arrays.toString(ultimasOperaciones) +
"]";
}
� Patrón de redefinición del método toString
Programación Orientada a Objetos 47Curso 2015/2016
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() + "[ … ]";
}
Programación Orientada a Objetos 48Curso 2015/2016
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 los depósitos garantizados que también son depósitos estructurados (incorrecto).
public void setTipoInteresVariable(double tipo,
Deposito[] depositos) {
for (Deposito d : depositos) {
if (d.getClass() == DepositoEstructurado.class) {
…
}
Programación Orientada a Objetos 49Curso 2015/2016
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.
� Clases abstractas.
� Interfaces.