PROG06 Programación Avanzada
PROG06 Programación Avanzada
PROG06 Programación Avanzada
PROGRAMACIÓN AVANZADA
Contenido:
1. Herencia _________________________________________________ 2
1.1. Conceptos _____________________________________________ 2
1.2. Implementación en Java _________________________________ 6
1.3. Sobrecarga y anulación de métodos _______________________ 6
1.4. Constructores __________________________________________ 7
1.5. Métodos finales ________________________________________ 8
1.6. Casting e instanceof ____________________________________ 9
2. Clases abstractas _________________________________________ 11
2.1. Métodos abstractos ___________________________________ 11
2.2. Clases abstractas ______________________________________ 13
3. La clase Object ___________________________________________ 14
3.1. Concepto _____________________________________________ 14
3.2. Método equals ________________________________________ 14
3.3. Método toString ______________________________________ 15
4. Interfaces Java ___________________________________________ 17
4.1. Concepto _____________________________________________ 17
4.2. Interface Comparable __________________________________ 20
1. Herencia
1.1. Conceptos
Sabemos que el Diagrama de Clases es el principal medio en UML para describir el
diseño del sistema.
Ejemplo de Asociación
PR
Ejemplo de Agregación
Ejemplo de Composición
La relación de Herencia se da entre dos clases cuando una de ellas representa una
especialización con respecto a la otra.
La clase especializada (clase “hija”) poseerá las características (métodos y
atributos) comunes que proceden de la clase general.
Ejemplo 1
Si diseñaras una clase que represente Animales, ésta podría tener como
operaciones: respirar, reproducirse o crecer.
Si ahora quisieras una clase para representar Leones, tendrías las mismas
operaciones de antes y algunas propias como correr y rugir.
Lo que hay que hacer NO ES definir de nuevo los métodos comunes, sino
simplemente indicar que un león es un animal.
Gracias a la herencia se consigue:
ESTRUCTURAR de forma elegante el diseño del sistema
REUTILIZAR el código de forma más eficiente
De esta forma se pueden crear nuevas clases partiendo de una clase ya existente.
Este tipo de relación es transitiva.
La necesidad de crear una nueva clase viene dada porque la clase existente no nos
permite crear objetos del modo que queremos.
Nuestra clase sí permitiría crear dichos objetos y por tanto, me aprovecho de la
clase existente pero la “personalizo” para poder crear el tipo de objeto que deseo.
Ejemplo 2
Tenemos implementada la clase Coche y ahora necesitamos la clase Motocicleta.
Como se puede ver, tienen prácticamente las mismas propiedades y operaciones.
NO sería práctico ni recomendable implementar todos los métodos de nuevo para
la clase Motocicleta.
PR
La solución sería crear una clase general Vehículo que contenga todas las
propiedades y operaciones comunes.
Además, para crear una clase nueva llamada Tractor, solo tendríamos que
indicar que hereda de Vehículo.
1.2. Implementación en Java
El API de Java es un conjunto de clases organizado jerárquicamente
mediante herencia.
Incluso las clases que nosotros creemos, son subclases de la clase Object.
En Java, la herencia se indica mediante la palabra clave extends.
public class Coche extends Vehiculo{
...
}
Los atributos y métodos cuyo modificador de acceso sea private o package
NO son heredados.
Para que puedan ser heredados deben usar el modificador de acceso
protected o public.
El modificador protected permite que el miembro sea heredable pero es
como el private, manteniendo la ocultación al utilizarlo en los atributos.
Además será normal que la subclase agregue sus atributos y métodos
propios.
1.3. Sobrecarga (Override) y anulación de métodos
Sabemos que las subclases heredan métodos de las superclases. En la subclase
tengo las siguientes posibilidades:
Implementar nuevos métodos que “especialicen” el comportamiento de la
subclase.
Sobrecargar métodos heredados. Tienen que tener exactamente la misma
cabecera.
Anular algún método heredado.
➢ Sobrecarga. Un ejemplo claro son los constructores de la clase
Será un nuevo método con el mismo nombre que uno heredado, pero con
distintos parámetros.
De esta manera puedes proporcionar una variante de un determinado
método, adaptado a las necesidades de la nueva clase.
PR
1.4. Constructores
Los constructores NO se heredan.
Cada clase debe proporcionar sus propios constructores. Lo que sí hay que tener en
cuenta es que:
Al crear un objeto de una clase, siempre se invoca primero el constructor
por defecto de la clase padre y después el suyo propio.
public class Motocicleta extends Vehiculo{
// Constructor sin parámetros
public Motocicleta(){
// Llamada implícita al constructor Vehiculo()
}
}
La llamada al constructor de la superclase siempre se realiza, incluso si no
has definido ningún constructor en tu subclase.
Si se desea invocar a un constructor diferente al de por defecto, se debe
realizar con la palabra reservada super.
En este caso es obligatorio usar super (marca) porque si no, llamaría
implícitamente al constructor sin parámetros con super().
public class Motocicleta extends Vehiculo{
// Constructor con 1 parámetro
public Motocicleta(String marca){
// Llamada constructor Vehiculo(marca)
super(marca);
}
}
➢ Reglas
Si la primera instrucción de un constructor de una subclase NO es una
invocación a otro constructor con this o super, Java añade de forma
invisible e implícita una llamada super() con la que invoca al constructor
por defecto de la superclase.
Luego continúa con las instrucciones de inicialización de los atributos de la
subclase.
Si en la superclase no hay constructor por defecto (sin parámetros),
ocurrirá un error en la compilación.
Si se invoca a constructores de superclases mediante super(…) en la
primera instrucción, entonces se llama al constructor seleccionado de la
superclase, luego inicializa los atributos de la subclase.
Si esa primera instrucción es una invocación a otro constructor de la clase
con this (…), entonces se llama al constructor seleccionado por medio de
this y realiza sus instrucciones.
El uso de super y this NO puede ser simultáneo puesto que ambos tienen
que ser la primera instrucción. Lo que significa que hay que elegir entre
ambas.
Ejemplo: https://www.arquitecturajava.com/java-constructores-y-super/
1.5. Métodos finales
Sabemos que una subclase puede:
sobrecargar métodos herededados
sobreescribir y anular métodos herededados
Pero una superclase puede estar diseñada para que algunos métodos siempre se
hereden en las subclases y por tanto NO puedan ser sobreescritos.
En este caso, en la superclase se habrá empleado la palabra final en la definición
del método.
PR
Ejemplo
public class Vehiculo {
// Método que puede ser sobreescrito
public void acelerar(int cantidad){
this.velocidad+=cantidad;
}
// Método que NO puede ser sobreescrito
public final void frenar(int cantidad){
this.velocidad-=cantidad;
}
}
1.6. Casting e instanceof
El casting es una conversión entre tipos compatibles. Por ejemplo entre int-char o
int-double.
Cuando tenemos dos objetos de clases distintas (son tipos distintos), el casting
entre ellos NO es posible, a no ser que tengan una relación de herencia.
Lo que consigues con la herencia es poder almacenar en una referencia de una
superclase (ejemplo Vehiculo), un objeto de una de sus subclases (Coche).
Al revés no sería posible.
Ejemplo
// Creo una referencia al objeto Vehiculo
Vehiculo refV = null;
// Creo dos objetos: un Coche y una Motocicleta
Coche unCoche = new Coche ("Audi", 5);
Motocicleta unaMoto = new Motocicleta();
// UNA referencia de la superclase puede almacenar la
// referencia a un objeto de la subclase
refV = unCoche; // un coche "ES UN" Vehiculo
//unaMoto = refV; // ERROR!!!! Un objeto subclase no puede
// almacenar la referencia de un objeto de la
// clase madre
Ten en cuenta que los objetos nunca cambian de tipo. En el ejemplo, con refV solo
se podrá invocar métodos propios de la clase Vehiculo, no de Coche, aunque
guarde la referencia a un Coche.
refV.acelerar(10);
refV.frenar(20);
refV.activarEPS(); // ¡ERROR! Por ser un método de Coche y no
// de Vehiculo
Uso de instanceof
Permite comprobar si un determinado objeto pertenece a una clase concreta
objeto instanceof clase
Imagina que tenemos un método que acepta como parámetro un objeto Vehiculo.
private static void diagnosticar(Vehiculo vv){
...
}
El parámetro podrá ser un Vehiculo o cualquier objeto de una subclase de
Vehiculo (un Coche, una Motocicleta, etc.) porque dicho objeto es un
Vehiculo.
Dentro del método, seguramente queramos manejar el objeto pasado e
invocar a sus métodos propios. Para ello necesitas hacer el casting.
// Supongo que es un coche, por lo que se convierte a Coche
Coche refCoche = (Coche) vv;
// Ahora se puede llamar a un método propio de Coche
refCoche.activarEPS();
Si el objeto pasado NO es un Coche, el casting produciría la Excepción
ClassCastException.
En general, para evitar este problema, antes de hacer un casting, debemos
comprobar qué tipo de objeto ha llegado en el parámetro. Esta operación
se hace con instanceof
if (vv instanceof Coche){
// Ahora puedo hacer CASTING con seguridad
Coche refCoche = (Coche) vv;
// Llamada a un método propio de Coche
refCoche.activarEPS();
}
else if (vv instanceof Motocicleta) {
// Ahora puedo hacer CASTING con seguridad
Motocicleta refMoto = (Motocicleta) vv;
}
Si se comprueba un objeto de una clase hija de la indicada usando
instanceof, el resultado será true.
En el ejemplo de arriba, si vv contiene un objeto cuya clase es hija de
Coche, la condición se evaluaría a cierta.
PR
2. Clases abstractas
2.1. Métodos abstractos
Observa el ejemplo:
private static void diagnosticar(Vehiculo vv){
if (vv instanceof Coche){
// Ahora puedo hacer CASTING con seguridad
Coche refCoche = (Coche) vv;
// Llamada a un método propio de Coche
refCoche.activarEPS();
}
...
}
Sabemos que mediante la referencia vv, solo se podrá llamar a métodos definidos
en la superclase.
Cuando una operación NO se puede implementar en la superclase porque es algo
más específico, debe implementarse en la subclase y para ser llamado debe
utilizarse instanceof.
Ese método específico, se podría implementar (o no) en todas las subclases, y en
cada una de ellas podría tener diferente nombre y/o parámetros, lo cual
dificultaría la llamada y habría que distinguir cada caso.
Ejemplo: calcularArea()
Si suponemos que el cálculo del área de un polígono regular es específico
de cada polígono, este método solo se puede implementar en las
subclases (Cuadrado, Triángulo, etc).
En un proceso donde se llame a dicho método, sería necesario saber qué
objeto es y qué método lo hace.
public static double sumaAreas(PoliRegular polis[]){
double suma=0.0;
Cuadrado refCua;
for (int i=0;i<polis.length;i++){
if (polis[i] instanceof Cuadrado){
refCua = (Cuadrado) polis[i];
// Es un Cuadrado, calculo su área
suma = suma + refCua.calcAreaCuadrado();
}
}
return suma;
}
Esta implementación es bastante problemática: un caso para cada tipo de
objeto.
¿Cuál sería la solución?
La solución sería que en todas las subclases de PoliRegular
existiera el método para calcular su área, y
se llamase de igual forma
public static double sumaAreas(PoliRegular polis[]){
double suma=0.0;
for (int i=0;i < polis.length;i++){
// Sabemos que todos los objetos
// que son PoliRegular (subclases),
// tienen el método calcularArea()
suma = suma + polis[i].calcularArea();
}
return suma;
}
La superclase puede obligar a las subclases a implementar un
determinado método con un nombre y una lista de parámetros.
Este método estará definido en la superclase como un método
abstracto, es decir, define un método teórico sin implementación en
la superclase, que DEBE IMPLEMENTARSE en TODAS las subclases
porque se hereda vacío.
Si sabes que todas las subclases implementan un método concreto,
podrás realizar métodos genéricos para objetos de la superclase, sin
importar qué objetos concretos son, puesto que sé que tienen el
método que necesito.
Ejemplo:
/**
* Método ABSTRACTO, definido en PoliRegular
* que calcula el área del polígono.
* DEBE ser implementado en cada subclase de forma
* específica para cada polígono.
* @return el área del polígono.
*/
abstract public double calcularArea();
PR
4. Interfaces Java
4.1. Concepto
Los interfaces son el mecanismo que utiliza Java para suplir la herencia múltiple.
Mediante la relación de herencia, vimos que si una clase padre define un método
abstracto, todas las clases hijas lo deben implementar (de forma obligatoria).
Cuando necesitamos obligar a que una clase implemente una serie de métodos, NO
es necesario que los herede de una clase que los contenga.
Simplemente, los métodos que queramos que una clase tenga implementados, se
agrupan en un interface (o varios) y se dice que la clase implementa dicho
interface.
Por tanto:
Un interface Java es un conjunto de métodos abstractos.
Un interface define un conjunto de operaciones, o lo que es lo mismo,
define un comportamiento.
Un interface NO define el tipo de objeto que es, sino lo que pueden hacer
una serie de objetos (de clases distintas).
Lo normal es que el nombre de los interfaces terminen con el sufijo
able/ible (Comparable, Configurable, Dibujable, Serializable, Pintable,
Arrancable, Imprimible, etc.).
Se dice que una clase implementa un interface cuando implementa
TODOS sus métodos abstractos.
De esta manera, sabiendo que una clase implementa una serie de métodos, será
posible codificar operaciones que de lo contrario no podría realizar.
Ejemplo: Interface Dibujable
/**
* Define la operación para que algo sea capaz de
* Dibujarse: método dibujar().
*/
public interface Dibujable (){
public void dibujar();
}
Ejemplo : Interface Arrancable
public interface Arrancable(){
protected boolean arrancado = false;
public void arrancar();
public void detener ();
}
Has aprendido ya que puedes tener un método general, cuyo parámetro sea un
objeto de la clase PoliRegular e invocar a un método que sabes que tiene cualquier
objeto que es un PoliRegular.
public static void metodoG(PoliRegular poli){
...
valor = poli.calcularArea();
}
Pues ahora vas a ver que también puedes tener un método general cuyo parámetro
sea un Interface, de modo que se podrá pasar un objeto de cualquier clase que
implemente dicho Interface (NO hace falta que tengan una clase padre común).
public class EjemploApp {
public static void mostrar(Dibujable forma){
forma.dibujar();
}
}
La clase Cuadrado implementa el Interface Dibujable y lo indica con la palabra clave
implements.
public class Cuadrado extends PoliRegular implements Dibujable {
/**
* Método abstracto de la clase PoliRegular, por eso es
* obligatorio implementarlo en la subclase Cuadrado
*/
public double calcularArea(){
return Math.pow(this.getMedidaLado(), 2);
}
/**
* Método del interface Dibujable, por eso, si se quiere
* utilizar es obligatorio implementarlo en la subclase
* Cuadrado
*/
public void dibujar(){
System.out.println (“Se dibuja un Cuadrado”);
System.out.println (“-----------”);
System.out.println (“- -”);
System.out.println (“- -”);
System.out.println (“-----------”);
}
}
La clase Muñeco es algo muy diferente a un PoliRegular, pero podría también
implementar el Interface Dibujable.
PR