Você está na página 1de 130

Tema 12.

Programación orientada a objetos: polimorfismo

Tema 12. Programación orientada a objetos: polimorfismo


y otras cosas de interés.

1. Introducción Polimorfismo
2. Otros ejemplos generales de polimorfismo
3. Demostración del comportamiento polimórfico
4. Clases y métodos abstractos
5. Ejemplo práctico: sistema de nómina utilizando polimorfismo.
6. Conversión de tipos: Casting y el operador instanceof
6.1 Conversión de tipos.
6.2 Operador instanceof.
6.3 Método getClass de las clase Object y método getName de la clase Class
6.4 Demostración en el procesamiento polimórfico del operador instanceof y la conversión
descendente
6.5 Resumen de las asignaciones permitidas entre variables de la superclase y de la
subclase

7. Métodos y clases final


8. Interfaces.
9. Ejemplo práctico: creación y uso de interfaces
9.1 Desarrollo de una jerarquía PorPagar
9.2 Declaración de la interfaz PorPagar
9.3 Creación de la clase Factura
9.4 Modificación de la clase Empleado para implementar la interfaz PorPagar
9.5 Modificación de la clase EmpleadoAsalariado para usarla en la jerarquía PorPagar

10. Declaración de constantes con interfaces


11. Interfaces comunes de la API de Java
12. Interface List
13. La clase Arraylist
13.1 Métodos de ARRAYLIST
13.2 Recorrer un Arraylist

14. Interface Comparable y método compareTo


15. Clases envolventes o wrappers
16 Resumen herencia-interfaz-polimorfismo.

Ejercicio I

1 - 130
Tema 12. Programación orientada a objetos: polimorfismo

1. Introducción Polimorfismo

Polimorfismo= Poli(múltiple) morfismo (formas)

Un mismo objeto puede tomar diversas formas.

El polimorfismo debe verse como una forma flexible de usar un grupo de objetos como si
fueran sólo uno.

La concordancia de tipos que hace posible el polimorfismo se puede lograr en java mediante:

• Jerarquía de Herencia
• Implementación de Interfaces

En primer lugar vamos a estudiar el polimorfismo con la jerarquía de herencia, después


veremos el polimorfismo con clases no relacionadas de forma jerárquica, a través de un
nuevo concepto: “interfaces”.

2 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La herencia permite implementar múltiples formas de un mismo método, dependiendo cada


una de ellas de la clase sobre la que se realice la implementación. Esta implementación
múltiple esta basada en el concepto de especialización de la herencia, donde cada clase hija,
sabe implementar mejor que el padre alguno o todos sus métodos.

El polimorfismo nos permite “programar en forma general”, en vez de “programar


en forma específica”. Nos permite escribir programas que procesen objetos que compartan la
misma superclase en una jerarquía de clases, como si todos fueran objetos de la superclase;
esto simplifica enormemente la programación.

3 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo de polimorfismo

Supón que creamos un programa que simula el movimiento de varios tipos de animales para
un estudio biológico. Las clases Pez, Rana y Ave representan los tres tipos de animales bajo
investigación. Imagina que cada una de estas clases extiende a la superclase Animal.

Animal superclase

Pez Rana Ave subclases

La superclase Animal contiene un método llamado mover que se implementa de forma


distinta en cada una de las subclases.

Cada subclase sobrescribe el método mover.

Cada tipo específico de Animal responde a un mensaje mover de manera única; un Pez
podría nadar un metro, una Rana podría saltar medio metro y un Ave podría volar 1 kilómetro.

El programa ejecuta el método mover de cada objeto animal de forma gneral, pero cada
objeto animal (pez, rana y ave) jecuta un método específico. Confiar en que cada objeto
sepa cómo “hacer lo correcto” (es decir, lo que sea apropiado para ese tipo de objeto)
en respuesta a la llamada al mismo método es el concepto clave del polimorfismo.

El mismo mensaje (en este caso, mover) que se envía a una variedad de objetos tiene
“muchas formas” de resultados; de aquí que se utilice el término polimorfismo.

Con el polimorfismo podemos diseñar e implementar sistemas que puedan


extenderse con facilidad; pueden agregarse nuevas clases con sólo modificar un poco
(o nada) las porciones generales de la aplicación, siempre y cuando las nuevas clases sean
parte de la jerarquía de herencia.

4 - 130
Tema 12. Programación orientada a objetos: polimorfismo

2.- Otros ejemplos generales de polimorfismo.

Ejemplo 1.

Si la clase Rectangulo se deriva de la clase Cuadrilatero, entonces un objeto Rectangulo es


una versión más específica de un objeto Cuadrilatero. Cualquier operación (por ejemplo,
calcular el perímetro o el área) que pueda realizarse en un objeto Cuadrilatero también puede
realizarse en un objeto Rectangulo.

Estas operaciones también pueden realizarse en otros objetos Cuadrilatero, como Cuadrado,
Paralelogramo y Trapezoide.

Cuadrilatero

Rectangulo Cuadrado Paralelogramo Trapezoide

Ejemplo 2.

Diseñamos un videojuego que manipule objetos de las clases Marciano,


Venusino, Plutoniano, NaveEspacial y RayoLaser. Imagina que cada clase hereda de la
superclase común llamada ObjetoEspacial, la cual contiene el método dibujar. Cada subclase
implementa a este método.

Un programa mantiene un array del tipo ObjetoEspacial con referencias a objetos de las
diversas clases mencionadas. Para refrescar la pantalla, el programa envía en forma
periódica el mismo mensaje a cada objeto; a saber, dibujar. No obstante, cada objeto
responde de una manera única.

La aplicación podría utilizar el polimorfismo para facilitar el proceso de agregar


nuevas clases, con el menor número de modificaciones del código.

Supón que deseamos agregar objetos Mercuriano a nuestro videojuego. Para ello, debemos
crear una clase Mercuriano que extienda a ObjetoEspacial y proporcione su propia
implementación del método dibujar.

Observación de ingeniería de software


El polimorfismo promueve la extensibilidad: el software que invoca el
comportamiento polimórfico es independiente de los tipos de los objetos a los
cuales se envían los mensajes.
Se pueden incorporar en un sistema nuevos tipos de objetos que pueden
responder a las llamadas de los métodos existentes, sin necesidad de modificar el
sistema base (main).

5 - 130
Tema 12. Programación orientada a objetos: polimorfismo

3.- Demostración del comportamiento polimórfico

Supongamos la siguiente jerarquía de clases:

Superclase ClaseA
ClaseA

Sublclase ClaseB

La ClaseA tiene el método: calcularX()

ClaseB La ClaseB tiene sobre-escrito el


método: calcularX()

public class ClaseA { ClaseA a1 =new ClaseA();


…. ClaseA a2 =new ClaseA();
}

a1, a2 son variables de la superclase (ClaseA) y referencian a objetos de


la superclase.
public class ClaseB extends ClaseA { ClaseB b1=new ClaseB();
…. ClaseB b2=new ClaseB();
}
b1, b2 son variables de la subclase (ClaseB) y referencian a objetos de la
subclase.

En los ejemplos del tema 11 (herencia) se llamaron a los métodos de la superclase utilizando
variables de la superclase y a los métodos de la subclase utilizando variables de la subclase.

a1.calularX(); b1.calcularX();
Se ejecuta el método de la Se ejecuta el método de la subclase
superclase con los valores del objeto con los valores del objeto
referenciado por la variable a1 referenciado por la variable b1

6 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Estas asignaciones son naturales y directas; las variables de la superclase están diseñadas
para referirse a objetos de la superclase, y las variables de la subclase están diseñadas para
referirse a objetos de la subclase.

También se puede invocar un método de la subclase a través de una variable del tipo de la
superclase, gracias a que un objeto de una subclase puede tratarse como un objeto de su
superclase, pero no viceversa.

ClaseA a1 =new ClaseA(); a1, a2 son variables de la superclase (ClaseA) y


ClaseA a2 =new ClaseA(); referencian a objetos de la superclase (ClaseA)

ClaseA a3 = new ClaseB(); a3, a4 son variables de la superclase (ClaseA) y


ClaseA a4 = b1; referencian a objetos de la subclase (ClaseB)

a3.metodoX(); Se ejecuta el método de la subclase con los valores del objeto


referenciado por la variable a3

El polimorfismo (en la utilización de la herencia) se produce cuando un


programa invoca a un método de una subclase a través de una variable de
la superclase.

En tiempo de ejecución, se hace una llamada a la versión correcta del método, dependiendo
de que la variable de la superclase guarde una referencia a un objeto de la superclase o a un
objeto de la sublclase.

a3= a1;
a3.metodoX(); Se ejecuta el método de la superclase

a3= b1;
a3.metodoX(); Se ejecuta el método de la subclase

7 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo 1. Utilizando la clase EmpleadoPorComisión y la clase EmpleadoBaseMasComisión que se


encuentra en la carpeta “ejemplo1”.

EmpleadoPorComisión superclase

EmpleadoBaseMasComisión subclase

En donde se sobrescribe el método toString() y el método ingresos.

Realiza una aplicación (PruebaPolimorfismo).

1º Invoque a un método de la variable de la clase (“lo normal”), sigue los siguientes pasos:

Crea un objeto de la superclase y asigna su referencia a una variable de la superclase.

Crea un objeto de la subclase y asigna su referencia a una variable de la subclase.

Llama a toString del objeto de la superclase , usando la variable de la superclase.

Llama a toString del objeto de la subclase , usando la variable de la subclase.

2º Invoque a un método de la subclase utilizando una variable de la superclase (polimorfismo),


realiza los siguientes pasos:

Crea un objeto de la subclase y asigna su referencia a una variable de la superclase. Observa


que esta operación esta permitida UpCasting.

Llama al método toString utilizando la variable de la superclase que se ha creado en el paso


anterior.

8 - 130
Tema 12. Programación orientada a objetos: polimorfismo

4 Clases y métodos abstractos

Cuando pensamos en un tipo de clase, asumimos que los programas crearán objetos de ese
tipo.

No obstante, en algunos casos es conveniente declarar clases para las cuales el


programador nunca creará instancias de objetos. A dichas clases se les conoce como
clases abstractas.

Como se utilizan sólo como superclases en jerarquías de herencia, nos referimos a ellas
como superclases abstractas.

Estas clases no pueden utilizarse para instanciar objetos.

9 - 130
Tema 12. Programación orientada a objetos: polimorfismo

El propósito de una clase abstracta es proporcionar una superclase apropiada, a partir de la


cual puedan heredar otras clases y, por ende, compartir un diseño común.

Las clases que pueden utilizarse para instanciar objetos se llaman clases concretas.
Dichas clases proporcionan implementaciones de cada método que declaran (algunas de las
implementaciones pueden heredarse).

Las superclases abstractas son demasiado generales como para crear objetos
reales; sólo especifican lo que tienen en común las subclases.

Algunas veces las clases abstractas constituyen varios niveles de la jerarquía. En el ejemplo
el primer y segundo nivel de la jerarquia son clases abstractas:

- Figura
- FiguraBidimensional
- FiguraTridimensional

El siguiente nivel de la jerarquía declara clases concretas: Circulo, Cuadrado, Triangulo,


Esfera, Cubo y Tetraedro.

Las figuras bidimensionales deben poder dibujarse, pero la clase abstracta


FiguraBidimensional no sabe qué dibujar, por lo que no puede implementar un verdadero
método dibujar, implementan un método dibujar abstracto.

Las clases concretas Circulo, Cuadrado, Triangulo, ….. sí implementan de forma concreta el
método dibujar.

10 - 130
Tema 12. Programación orientada a objetos: polimorfismo

No todas las jerarquías de herencia contienen clases abstractas. Sin embargo, a menudo los
programadores escriben código utilizando superclases abstractas. Por ejemplo, un
programador puede escribir un método con un parámetro de un tipo de superclase abstracta.
Cuando se llama, ese método puede recibir un objeto de cualquier clase concreta que herede
en forma directa o indirecta a la superclase abstracta.

Supongamos una superclase abstracta Vehiculo, el método insertarVehiculo tiene un


parámetro del tipo de la superclase abstracta, cuando se llame recibirá un objeto de una
clase concreta (Moto, Coche y Camioneta) que hereda de la superclase abstracta Vehiculo.

public void insertarVehiculo (Vehiculo param){

Para hacer una clase abstracta, ésta se declara con la palabra clave abstract:

public abstract class NombreDeClase { ….}

Por lo general, una clase abstracta contiene uno o más métodos abstractos. Un método
abstracto tiene la palabra clave abstract en su declaración:

public abstract void dibujar(); // método abstracto

Los métodos abstractos no proporcionan implementaciones, no tienen cuerpo.

Una clase que contiene métodos abstractos debe declararse como clase
abstracta, aun si esa clase contiene métodos concretos (no abstractos).

Cada subclase concreta de una superclase abstracta debe proporcionar


implementaciones de los métodos abstractos de la superclase.

11 - 130
Tema 12. Programación orientada a objetos: polimorfismo

En las jerarquías de herencia con clases abstractas las subclases heredan la “interfaz” que
representa sus métodos abstractos y la implementación de sus métodos concretos.

Los constructores y los métodos static no pueden declararse como abstract.

Observación de ingeniería de software y programación sobre clases y métodos abstractos:

(1) Una clase abstracta declara los atributos y comportamientos comunes


(métodos) de las diversas clases en una jerarquía de clases. Por lo general, una
clase abstracta contiene uno o más métodos abstractos, que las subclases deben
sobrescribir, si van a ser concretas.

(2) Los atributos y los métodos concretos de una clase abstracta están sujetos a
las reglas normales de la herencia.

(3) Tratar de instanciar un objeto de una clase abstracta es un error de


compilación.

(4) Si no se implementan los métodos abstractos de la superclase en una clase


derivada, se produce un error de compilación, a menos que la clase derivada
también se declare como abstract.

(5) Podemos usar superclases abstractas para declarar variables que puedan
guardar referencias a objetos de cualquier clase concreta que se derive de esas
superclases abstractas. Los programas, por lo general, utilizan dichas variables
para manipular los objetos de las subclases mediante el polimorfismo.

12 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5 Ejemplo práctico: sistema de nómina utilizando polimorfismo.

Supongamos una empresa que paga a sus empleados por semana. Los empleados son
de cuatro tipos:

- Empleados asalariados (EmpleadoAsalariado) que reciben un salario semanal fijo.

- Empleados por horas (EmpleadoPorHora) que reciben un sueldo por hora y pago
por tiempo extra, todas las horas trabajadas que excedan a 40 horas.

- Empleados por comisión (EmpleadoPorComision) que reciben un porcentaje de


sus ventas.

- Empleados por comisión con salario base (EmpleadoBaseMasComision), reciben


un porcentaje de sus ventas más un salario base.

Utilizamos la clase abstract Empleado para representar el concepto general de un


empleado. Las clases EmpleadoAsalariado, EmpleadoPorComision y EmpleadoPorHoras
extienden a Empleado.

La clase EmpleadoBaseMasComision extiende a EmpleadoPorComision.

El diagrama de clases de UML muestra la jerarquía de herencia:

13 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La siguiente figura muestra la “interfaz” polimórfica para cada una de las cinco clases en
la jerarquía de Empleado.

Todos los empleados tiene un nombre, un primer apellido, un segundo apellido y un


número de seguridad social, por lo que se definen los siguientes atributos en la
superclase abstracta Empleado:

- nombre
- apellidoPrimero
- apellidoSegundo
- numeroSeguroSocial

14 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo 2. En un nuevo paquete (ejemplo2), crea la jerarquía de la clase Empleado


siguiendo las indicaciones de los siguientes apartados:

5.1 Creación de la superclase abstracta Empleado

5.2 Creación de la subclase concreta EmpleadoAsalariado

5.3 Creación de la subclase concreta EmpleadoPorHoras

5.4 Creación de la subclase concreta EmpleadoPorComision

5.5 Creación de la subclase concreta EmpleadoBaseMasComision

5.6 Demostración del procesamiento polimórfico, aplicación “Nomina”

15 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.1 Creación de la superclase abstracta Empleado

La clase Empleado tiene el siguiente constructor.

// constructor con cuatro argumentos


public Empleado( String nombre, String apellido1, String apellido2, String nss ) {
this.nombre = nombre;
apellidoPrimero = apellido1;
apellidoSegundo = apellido2;
numeroSeguroSocial = nss;
}

Además la clase Empleado proporciona los métodos ingresos y toString, y también los
métodos obtener y establecer que manipulan sus variables de instancia.

Los métodos establecer (setPrimerNombre, setApellidoPrimero, setApellidoSegundo,


setNumeroSeguroSocial) no realizan ningún tipo de comprobación.

Es evidente que un método ingresos se aplica en forma genérica a todos los empleados.
Pero cada cálculo de los ingresos depende de la clase de cada empleado. Por lo tanto,
declaramos el método ingresos como abstract.

Cada una de las subclases redefine a ingresos con una implementación apropiada.

// método abstracto sobrescrito por las subclases


public abstract double ingresos(); // aquí no hay implementación

Al incluir a ingresos como un método abstracto en Empleado, se obliga a cada subclase


directa de Empleado a sobrescribir el método ingresos para poder convertirse en una
clase concreta. Esto permite al diseñador de la jerarquía de clases demandar que cada
subclase concreta proporcione un cálculo apropiado.

16 - 130
Tema 12. Programación orientada a objetos: polimorfismo

El método toString en la clase Empleado devuelve un String que contiene el nombre, el


primer apellido y el número de seguro social del empleado.

public String toString() {


return String.format( "%s %s\nnúmero de seguro social: %s",
getNombre(), getApellidoPrimero(), getNumeroSeguroSocial() );
}

Este String tiene los datos comunes de todos los empleados.

Como veremos más adelante, cada subclase de Empleado sobrescribe el método toString
para crear una representación String de un objeto de esa clase que contiene el tipo del
empleado ("empleado asalariado:", "empleado por horas:", "empleado por comisión:",
"empleado con salario base más comisión:"), seguido del resto de su información (según
tipo de empleado).

17 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.2 Creación de la subclase concreta EmpleadoAsalariado

La clase EmpleadoAsalariado extiende a la clase Empleado, sobrescribe el método


ingresos , lo cual convierte a EmpleadoAsalariado en una clase concreta.

public class EmpleadoAsalariado extends Empleado {

private double salarioSemanal;

….
}

La clase incluye, además de la variable de instancia salarioSemanal, los siguientes


métodos:

Un constructor que recibe un nombre, un primer apellido, un segundo apellido, un número


de seguridad social y un salario semanal como argumentos. El constructor pasa el
nombre, el primer apellido, el segundo apellido y el número de seguridad social al
constructor de Empleado para inicializar los atributos de la superclase.

public EmpleadoAsalariado( String nombre, String apellido1, String apellido2,


String nss, double salario ) {
super( nombre, apellido1, apellido2, nss ); // pasa al constructor de Empleado
setSalarioSemanal( salario ); // valida y almacena el salario
}

Un método establecer y obtener para el atributo salarioSemanal.

Un método ingresos que sobrescribe el método abstracto ingresos de Empleado para


proporcionar una implementación concreta que devuelva el salario semanal del
EmpleadoAsalariado.

// sobrescribe el método abstracto de Empleado


public double ingresos() {
return getSalarioSemanal();
}

Nota: si no implementamos ingresos, la clase EmpleadoAsalariado


debe declararse como abstract; en caso contrario, se produce un error de
compilación .

Un método toString que devuelve un objeto String que incluye el tipo del empleado, a
saber, "empleado asalariado: ", seguido de la información del empleado producida por el
método toString de la superclase Empleado y el salario semanal ( getSalarioSemanal).

// devuelve representación String de un objeto EmpleadoAsalariado


public String toString() {
return String.format( "\nempleado asalariado: %s\n%s: %,.2f€",
super.toString(), "salario semanal", getSalarioSemanal() );
}

18 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.3 Creación de la subclase concreta EmpleadoPorHoras

La clase extiende a la clase Empleado, tiene dos variables de instancia:

- sueldo: precio de la hora de trabajo (€/hora).


- horas: horas trabajadas que deben cobrar.

public class EmpleadoPorHoras extends Empleado {


private double sueldo; // sueldo por hora
private double horas; // horas trabajadas por semana
….....
….....

….....
}

La clase incluye un constructor que recibe como argumentos un nombre, un primer


apellido, un segundo apellido, un número de seguridad social, un sueldo por horas
(sueldo) y el número de horas trabajadas (horas).

// constructor con seis argumentos


public EmpleadoPorHoras( String nombre, String apellido1, String apellido2,
String nss, double sueldoPorHoras, double horasTrabajadas ) {

super( nombre, apellido1, apellido2, nss );


setSueldo( sueldoPorHoras ); // valida y almacena el sueldo por horas
setHoras( horasTrabajadas ); // valida y almacena las horas trabajadas
}

El método setSueldo asegura que sueldo sea positivo, y el método setHoras asegura que
horas esté entre 0 y 168 (el número total de horas en una semana), inclusive.

La clase EmpleadoPorHoras también incluye métodos obtener para devolver los valores
de sueldo y horas, respectivamente.

Un método ingresos para calcular los ingresos de un EmpleadoPorHoras, las primeras 40


horas se cobran al precio de la hora (sueldo). Las horas extras (las que se hacen por
encima de las 40 horas semanales) se cobran a un precio un 50% superior.

// calcula los ingresos; sobrescribe el método abstracto ingresos en Empleado


public double ingresos() {
if ( getHoras() <= 40 ) // no hay tiempo extra
return getSueldo() * getHoras();
else
return 40 * getSueldo() + ( getHoras() - 40 ) * getSueldo() * 1.5;
}

19 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Y un método toString, que devuelve el tipo del empleado, a saber, "empleado por horas: ",
e información específica para ese Empleado.

// devuelve representación String de un objeto EmpleadoPorHoras


public String toString() {
return String.format( "\nempleado por horas: %s\n%s: %,.2f€; %s: %,.2f ",
super.toString(), "sueldo por hora", getSueldo(),
"horas trabajadas", getHoras() );
}

En el siguiente enlace puedes encontrar información sobre el método format o printf:

http://puntocomnoesunlenguaje.blogspot.com.es/2012/08/java-printf.html

20 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.4 Creación de la subclase concreta EmpleadoPorComision

La clase EmpleadoPorComision extiende a la clase Empleado. Tiene las variables de


instancia ventasBrutas y tarifaComision.

public class EmpleadoPorComision extends Empleado {


private double ventasBrutas; // ventas totales por semana
private double tarifaComision; // porcentaje de comisión

…..
}

Esta clase incluye a un constructor que recibe como argumentos nombre, apellido1,
apellido2, un número de seguro social, importe de ventas y una tarifa de comisión.

// constructor con seis argumentos


public EmpleadoPorComision( String nombre, String apellido1, String apellido2,
String nss, double ventas, double tarifa ) {
super( nombre, apellido1,apellido2, nss );
setVentasBrutas( ventas );
setTarifaComision( tarifa );
}

Métodos establecer para asignar nuevos valores a las variables de instancia


tarifaComision y ventasBrutas:

public void setTarifaComision( double tarifa ) {


tarifaComision = ( tarifa > 0.0 && tarifa < 1.0 ) ? tarifa : 0.0;
}

public void setVentasBrutas( double ventas ) {


ventasBrutas = ( ventas < 0.0 ) ? 0.0 : ventas;
}

Métodos obtener que obtienen los valores de estas variables de instancia.

El método ingresos para calcular los ingresos de un EmpleadoPorComision.

public double ingresos() {


return getTarifaComision() * getVentasBrutas();
}

Y el método toString

public String toString() {


return String.format( "\n%s: %s\n%s: %,.2f€; %s: %.2f",
"empleado por comisión", super.toString(),
"ventas brutas", obtenerVentasBrutas(),
"tarifa de comisión", obtenerTarifaComision() );
}

21 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.5 Creación de la subclase concreta EmpleadoBaseMasComision

La clase EmpleadoBaseMasComision extiende a la clase EmpleadoPorComision y, por lo


tanto, es una subclase indirecta de la clase Empleado.

public class EmpleadoBaseMasComision extends EmpleadoPorComision {


private double salarioBase; // salario base por semana

…...
}

La clase EmpleadoBaseMasComision tiene un constructor que recibe como argumentos


nombre, apellido1, apellido2, número de seguridad social, importe de ventas, tarifa de
comisión y un salario base.

public EmpleadoBaseMasComision( String nombre, String apellido1,


String apellido2,String nss, double ventas, double tarifa, double salario ) {
super( nombre, apellido1, apellido2, nss, ventas, tarifa );
setSalarioBase( salario ); // valida y almacena el salario base
}

El método setSalario valida y almacena el salario base.

public void setSalarioBase( double salario ) {


salarioBase = ( salario < 0.0 ) ? 0.0 : salario; // positivo
}

El método ingresos; sobrescribe el método ingresos en EmpleadoPorComision

public double ingresos() {


return getSalarioBase() + super.ingresos();
}

El método toString de EmpleadoBaseMasComision:

/ / devuelve representación String de un objeto EmpleadoBaseMasComision


public String toString() {
return String.format( "\n%s %s; %s: %,.2f€", "con salario base",
super.toString(), "salario base", getSalarioBase() );
}

22 - 130
Tema 12. Programación orientada a objetos: polimorfismo

5.6 Demostración del procesamiento polimórfico, aplicación “Nomina”

Para probar nuestra jerarquía de Empleado, la aplicación Nomina crea un objeto de cada
una de las cuatro clases concretas: EmpleadoAsalariado, EmpleadoPorHoras,
EmpleadoPorComision y EmpleadoBaseMasComision.

Por ejemplo:

// crea objetos de las subclases


EmpleadoAsalariado empleadoAsalariado =
new EmpleadoAsalariado( "Pepe", "Martín", "Fulano", "111-11-1111", 800.00 );

EmpleadoPorHoras empleadoPorHoras =
new EmpleadoPorHoras( "Catalina", "Fernández", "Fernández","222-22-2222", 20.75, 45 );

EmpleadoPorComision empleadoPorComision =
new EmpleadoPorComision( "Susana", "Pómez", "Galindo", "333-33-3333", 10000, 0.06 );

EmpleadoBaseMasComision empleadoBaseMasComision =
new EmpleadoBaseMasComision( "Bob", "Nieto", "Bolaños", "444-44-4444", 5000, 0.04, 300 );

El programa manipula estos objetos mediante el polimorfismo, utilizando un array de


variables Empleado que deberá lógicamente definirse e inicializar con objetos Empleado
(recuerda que las subclases son también objetos empleados).

// crea un array Empleado de cuatro elementos


Empleado arrayEmpleados[] = new Empleado[ 4 ];

// inicializa el array con objetos Empleado (referencias a subclases)


arrayEmpleados[ 0 ] = empleadoAsalariado;
arrayEmpleados[ 1 ] = empleadoPorHoras;
arrayEmpleados[ 2 ] = empleadoPorComision;
arrayEmpleados[ 3 ] = empleadoBaseMasComision;

Procesa el array mediante polimorfismo, para obtener la representación de cada


empleado (método toString) y sus ingresos:

for ( int contador=0; contador < arrayEmpleados.length; contador++) {


System.out.println( arrayEmpleados[contador] ); // invoca a toString
System.out.printf( "ingresos %,.2f€",arrayEmpleados[contador].ingresos() );
System.out.println();
}

Todas las llamadas a los métodos toString e ingresos se resuelven en tiempo de


ejecución, con base en el tipo del objeto al que arrayEmpleados[contador] hace
referencia. Este proceso se conoce como vinculación dinámica o vinculación postergada.

Como resultado de la vinculación dinámica, Java decide qué método toString e ingresos
llamará en tiempo de ejecución, Java procesa estas llamadas a los métodos en forma
polimórfica.

23 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Resultado de la ejecución:

empleado asalariado: Pepe Martín


número de seguro social: 111-11-1111
salario semanal: 800,00€
ingresos 800,00€

empleado por horas: Catalina Fernández


número de seguro social: 222-22-2222
sueldo por hora: 20,75€; horas trabajadas: 45,00
ingresos 985,63€

empleado por comisión: Susana Pómez


número de seguro social: 333-33-3333
ventas brutas: 10.000,00€; tarifa de comisión: 0,06
ingresos 600,00€

con salario base


empleado por comisión: Bob Nieto
número de seguro social: 444-44-4444
ventas brutas: 5.000,00€; tarifa de comisión: 0,04; salario base: 300,00€
ingresos 500,00€

24 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo 3. Realiza una pequeña mejora en el código del programa


“Nomina” en una nueva clase “CopyOfNomina”; utiliza la
instrucción for mejorada que itera a través de los elementos del
array sin utilizar un contador (tema 9.-Arrays).

La sintaxis de una instrucción for mejorada es:

for ( parámetro : nombreArray )


instrucción

En donde parámetro tiene dos partes: un tipo y un identificador


(por ejemplo, int numero), y nombreArray es el array a través del
cual se iterará. El tipo del parámetro debe concordar con el tipo
de los elementos en el array.

for ( int contador=0; contador < arrayEmpleados.length; contador++) {

System.out.println( arrayEmpleados[contador] ); // invoca a toString


System.out.printf( "ingresos %,.2f€",arrayEmpleados[contador].ingresos() );
System.out.println();

for ( Empleado empleadoActual : arrayEmpleados ) {

System.out.println( empleadoActual ); // invoca a toString


System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );
System.out.println();

25 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo4. Jerarquía de clases, utilizamos una clase abstracta ClaseA y las clases que la
extienden ClaseB, ClaseC y ClaseD

ClaseA

ClaseB ClaseC ClaseD

ClaseB, ClaseC y ClaseD sobrescriben el método abstracto saludo () de la superclase;


saludando en gallego (claseB), euskera (ClaseC) y catalán(ClaseD).

public abstract class ClaseA {


public abstract String saludo();

//Saludo Gallego
public class ClaseB extends ClaseA{
public String saludo(){
return "Ola";
}

//Saludo Euskera
public class ClaseC extends ClaseA{
public String saludo(){
return "kaixo";
}

//Saludo Catalán
public class ClaseD extends ClaseA{
public String saludo(){
return "Hola";
}

Guarda en un array de variables de ClaseA una referencia a cada clase concreta (objeto);
y genera un número aleatorio para obtener un saludo en una determinada lengua.
Pregunta antes de escribir el método el tipo de objeto que representa la variable de la
superclase y según proceda escribe antes del saludo un mensaje (“Eu son de
Galicia”,“Sóc de catalunya” o “Ni Euskadi naiz”).

Lógicamente, y como tú bien sabes, la operativa de esta aplicación se podría resolver sin
utilizar herencia, polimorfismo y las técnicas de POO, es (como muchas otras) una
aplicación didáctica para aplicar conceptos de POO.

26 - 130
Tema 12. Programación orientada a objetos: polimorfismo

6. Conversión de tipos: Casting y el operador instanceof

6.1 Conversión de tipos

Cuando procesamos objetos en forma polimórfica en una jerarquía de herencia , podemos


ejecutar desde la variable superclase métodos sobrescritos de la subclases, pero no se
permite ejecutar métodos que pertenezcan sólo a la subclase desde una
referencia a la superclase.

public class ClaseA {


public void xxx(){
ClaseA System.out.println("Hola desde A");
}
}
+ xxx()

public class ClaseB extends ClaseA{


public void xxx(){
System.out.println("Hola desde B");
ClaseB }
public void yyy(){
System.out.println("Adiós desde B");
+ xxx() }
+ yyy() }

public class PruebaCorrectaIncorrecta {

public static void main(String[] args) {


ClaseA a1;
ClaseB b1 =new ClaseB();
a1=b1; // ClaseA a1= new ClaseB();
/* ¿Se pueden ejecutar los métodos de la subclase utilizando la
variable de la superclase de forma polimórfica? Depende */
a1.xxx(); //SI
a1.yyy(); //NO
}

public class PruebaCorrectaIncorrecta {

public static void main(String[] args) {


ClaseA a1;
ClaseB b1 =new ClaseB();
a1=b1;
a1.xxx(); // polimorfismo, no necesita conversión
((ClaseB) a1).yyy(); // necesita conversión
}

27 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Explicación: la variable a1 es del tipo ClaseA (superclase).

La variable a1 mediante polimorfismo hace referencia al método sobrescrito xxx() de la


clase ClaseB, pero no puede hacer referencia al método yyy() porque dicho método no
existe en la ClaseA.

El tratamiento polimórfico no puede utilizar la variable de la superclase


para hacer referencia a un método o atributo especifico de la subclase.

Solución: Java permite una conversión descendente de la variable de ClaseA (superclase)


a una variable de ClaseB (subclase).

La conversión de tipos se denomina casting, y sirve para transformar una variable


primitiva de un tipo a otro o una variable de una clase a otra clase.

Hay dos tipos de casting:

UpCasting: conversión de un tipo en otro superior en la jerarquía de clases. No


hace falta especificarlo.

DownCasting: conversión de un tipo en otro inferior en la jerarquía de clases. Se


especifica precediendo al objeto a convertir con el nuevo tipo entre paréntesis.

(ClaseB) a1;

28 - 130
Tema 12. Programación orientada a objetos: polimorfismo

6.2 Operador instanceof

El operador instanceof sirve para consultar si la referencia que guarda una variable
(objeto) es una instancia de una clase determinada

(var instanceof ClaseA)

// devuelve true si var es una referencia a un objeto de la clase ClaseA

6.3 Método getClass de las clase Object y método getName de la clase Class.

El método getClass() de la clase Object

El método getClass() es un método final (no puede sobreescribirse) que devuelve una
representación en tiempo de ejecución de la clase del objeto.
Este método devuelve un objeto de tipo Class que permite el uso de los métodos
definidos en la clase Class sobre ese objeto y que nos pueden dar diversa información
(nombre, el nombre de su superclase, los nombres de los interfaces que implementa, … ).

El método getName() de la clase Class.


Devuelve (String) el nombre de la clase de un objeto Class.
http://docs.oracle.com/javase/7/docs/api/

Prueba el siguiente código utilizando la jerarquía de clases del ejemplo 4:

public static void main(String[] args) {


ClaseB objetoB= new ClaseB();
ClaseC objetoC= new ClaseC();
ClaseD objetoD= new ClaseD();
ClaseA arrayClaseA[] = {objetoB, objetoC, objetoD};
for ( ClaseA objeto : arrayClaseA ) {
System.out.println( objeto.getClass() );
System.out.println( objeto.getClass().getName() );
System.out.println();
}
}

29 - 130
Tema 12. Programación orientada a objetos: polimorfismo

6.4 Demostración en el procesamiento polimórfico del operador instanceof


y la conversión descendente: Ejemplo5 ( CopyOfNomina2).

En la aplicación Nomina se invoca al método “ingresos()” y “toString()” utilizando el


polimorfismo.

for ( Empleado empleadoActual : arrayEmpleados ) {

System.out.println( empleadoActual ); // invoca a toString


System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );
System.out.println();

Realiza una nueva aplicación CopyOfNomina2, que antes de escribir los ingresos en la
estructura repetitiva “for” modifica el salario base (incrementa en un 10%) si el objeto es
del tipo EmpleadoBaseMasComision.

Realiza las pruebas para comprobar que se ha realizado el cálculo correcto en los
ingresos de todos los objetos.

30 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Solución ejemplo5

En la práctica se deben de solucionar dos problemas:

Primero, saber que tipo de referencia tiene la variable de la superclase empleadoActual


en tiempo de ejecución, ya que sólo se desea modificar el atributo salarioBase de los
objetos tipo EmpleadoBaseMasComision.

Para ello se utiliza el operador instanceof:

empleadoActual instanceof EmpleadoBaseMasComision

Esta operación es verdadera si el objeto al que hace referencia empleadoActual es un


EmpleadoBaseMasComision.

En el siguiente código realizamos un procesamiento con los objetos


EmpleadoBasePorComision; a medida que los encontramos, incrementamos su salario
base en un 10%.

for ( Empleado empleadoActual : arrayEmpleados ) {


System.out.println( empleadoActual ); // invoca a toString

// determina si el elemento es un EmpleadoBaseMasComision


if ( empleadoActual instanceof EmpleadoBaseMasComision ) {
// incrementamos salario base
. . .

}
System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );
System.out.println();
}

31 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La condición
if ( empleadoActual instanceof EmpleadoBaseMasComision ) {

es verdadera si el objeto al que hace referencia empleadoActual es un


EmpleadoBaseMasComision. Y también sería verdadero para cualquier objeto de una
subclase (si la hubiera) de EmpleadoBaseMasComision, debido a la relación “es un” que
tiene una subclase con su superclase.

Segundo, conversión descendente.

¿Qué pasa si queremos en el tratamiento polimórfico utilizar la variable de la superclase


para hacer referencia a un método o atributo especifico de la subclase?

Si implementamos el incremento del salario de la siguiente forma:

for ( Empleado empleadoActual : empleados ) {


System.out.println( empleadoActual ); // invoca a toString

// determina si el elemento es un EmpleadoBaseMasComision


if ( empleadoActual instanceof EmpleadoBaseMasComision ) {
// incrementamos salario base
double salarioAnterior= empleadoActual.getSalarioBase();
empleadoActual.setSalarioBase(salarioAnterior*1.10);

System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );


System.out.println();
}

32 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Se produce un error:

Error: The method getSalarioBase() is undefined for the type Empleado

El método getSalarioBase() es de la clase EmpleadoBaseMasComision, la operación no


se puede realizar, es un error de compilación.

Tampoco se puede asignar una variable de la superclase a una variable de la subclase sin
una conversión descendente explícita, es un error de compilación.

Para hacer la operación hay que utilizar la técnica DownCasting: conversión de un tipo en
otro inferior en la jerarquía de clases.

La conversión del tipo Empleado al tipo EmpleadoBaseMasComision se permite sólo si el


objeto al que hace referencia la variable de tipo Empleado “es un”
EmpleadoBaseMasComision o tiene una relación “es un” con
EmpleadoBaseMasComision.

Para ello se utiliza el operador instanceof para determinar si el tipo de referencia que
guarda la variable de la superclase es un objeto del tipo EmpleadoBaseMasComision.

if ( empleadoActual instanceof EmpleadoBaseMasComision )

(EmpleadoBaseMasComision)empleadoActual

33 - 130
Tema 12. Programación orientada a objetos: polimorfismo

for ( Empleado empleadoActual : arrayEmpleados ) {


System.out.println( empleadoActual ); // invoca a toString
// determina si el elemento es un EmpleadoBaseMasComision
if ( empleadoActual instanceof EmpleadoBaseMasComision ) {

// incrementamos salario base


double salarioAnterior=((EmpleadoBaseMasComision)empleadoActual).getSalarioBase();
((EmpleadoBaseMasComision) empleadoActual).setSalarioBase(salarioAnterior*1.10);

}
System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );
System.out.println();
}

Otra forma: hacemos una conversión descendente de la variable y se la asignamos a una


nueva variable de ese tipo.

EmpleadoBaseMasComision empleado = ( EmpleadoBaseMasComision ) empleadoActual;

double salarioBaseAnterior = empleado.getSalarioBase();


empleado.setSalarioBase( salarioBaseAnterior * 1.10 );

34 - 130
Tema 12. Programación orientada a objetos: polimorfismo

for ( Empleado empleadoActual : empleados ) {


System.out.println( empleadoActual ); // invoca a toString

// determina si el elemento es un EmpleadoBaseMasComision


if ( empleadoActual instanceof EmpleadoBaseMasComision ) {
// conversión descendente de la referencia de Empleado
// a una referencia de EmpleadoBaseMasComision
EmpleadoBaseMasComision empleado =
( EmpleadoBaseMasComision ) empleadoActual;

// incrementamos salario base


double salarioBaseAnterior = empleado.getSalarioBase();
empleado.setSalarioBase( 1.10 * salarioBaseAnterior );

}
System.out.printf( "ingresos %,.2f€", empleadoActual.ingresos() );
System.out.println();

35 - 130
Tema 12. Programación orientada a objetos: polimorfismo

6.5 Resumen de las asignaciones permitidas entre variables de la superclase y de la


subclase.

Aunque una variable de una subclase también es una variable de su superclase, las dos
variables son distintas. Las variables de la superclase no pueden hacer referencia a
métodos o atributos especificos de la subclase.

Resumen de lo que se puede y no se puede hacer con los objetos y variables de las
superclases y las subclases:

ClaseA a1, a2;


a1, a2…. variables de la superclase
ClaseA
(referencias a objetos de la superclase)

ClaseB b1, b2;


ClaseB b1, b2 … variables de la subclase
(referencias a objetos de la subclase)

1. Asignar una referencia de la superclase a una variable de la superclase es un proceso


simple y directo. “Lo normal”

a1 = a2;

2. Asignar una referencia de la subclase a una variable de la subclase es un proceso


simple y directo. “Lo normal”

b1= b2;

3. Asignar una referencia de la subclase a una variable de la superclase es seguro, ya que


el objeto de la subclase es un objeto de su superclase.
a1=b1;

La variable de la superclase se puede utilizar para hacer un tratamiento polimórfico.


No obstante, si la variable de la superclase hace referencia a los miembros que
pertenezcan sólo a la subclase, se producen errores de compilación.

36 - 130
Tema 12. Programación orientada a objetos: polimorfismo

4. Tratar de asignar una variable de la superclase a una variable de la subclase


produce un error de compilación. Para evitar este error se debe hacer una conversión.

b1=a1; //error de compilación

b1= (ClaseB) a1; //¿error?: Depende del contenido de a1:


si (a1 guarda una referencia a un objeto de tipo b1)
no se produce error
si no
se produce error en tiempo de ejecución ( una
excepción)

En el tratamiento polimórfico el operador instanceof debe utilizarse para asegurar que


dicha conversión se realiza sólo si el objeto es de la subclase.

37 - 130
Tema 12. Programación orientada a objetos: polimorfismo

7. Métodos y clases final

Se pueden declarar variables, métodos, parámetros de los métodos y clases con el


modificador final.

Las variables que se declaran como final son constantes, no pueden modificarse una vez
que se inicializan.

Las constantes en Java se suelen definir mediante la combinación de las keyword:


static y final.

Ejemplo: public static final double PI = 3.141592653589;

Métodos final.

Definiendo un método como final conseguimos que ninguna otra clase pueda sobrescribirlo.

Un método que se declara como final en una superclase no puede sobrescribirse en una
subclase.

- Los métodos que se declaran como private son implícitamente final, ya que es
imposible sobrescribirlos en una subclase.

38 - 130
Tema 12. Programación orientada a objetos: polimorfismo

- Los métodos que se declaran como static son implícitamente final.

La declaración de un método final nunca puede cambiar, por lo cual todas las subclases
utilizan la misma implementación del método, y las llamadas a los métodos final se
resuelven en tiempo de compilación. Esto se le conoce como vinculación estática.

(Como el compilador sabe que los métodos final no se pueden sobrescribir, puede
optimizar los programas eliminando las llamadas a los métodos final, y reemplazándolas
con el código expandido de sus declaraciones en la ubicación de cada una de las
llamadas a los métodos).

Clase Final.

Definiendo una clase como final conseguimos que ninguna otra clase pueda
heredar de ella.

La clase String es un ejemplo de una clase final.

Una clase que se declara como final no puede ser una superclase (es decir, una clase no
puede extender a una clase final).

- Tratar de declarar una subclase de una clase final es un error de compilación.

Todos los métodos en una clase final son implícitamente final.

Al hacer la clase final también se evita que los programadores creen subclases que
podrían ignorar las restricciones de seguridad.

39 - 130
Tema 12. Programación orientada a objetos: polimorfismo

8. Interfaces.

En apartados anteriores hemos estudiado que una clase sólo hereda de la superclase, y
hemos aplicado el concepto de polimorfismo a la herencia.

Java no implementa la herencia múltiple (una clase hereda de más de una


clase).

Por lo tanto, ¿un esquema como el siguiente en Java seria posible?

El esquema de la figura representaría que hubiera clases como ProfesorEmerito que


heredarían de dos clases: Profesor y Conferenciante. Esto sería un caso de herencia
múltiple, y representaría que la subclase comparte las características de las dos
superclases, y además tiene sus características específicas propias.

Las interfaces posibilitan el esquema anterior.

40 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Los interfaces son un tipo de clase especial que no implementa ninguno


de sus métodos. Todos son abstractos. Por tanto no se pueden
instanciar.

La declaración de un interface Java se realiza mediante la palabra reservada: interface,


seguido de su nombre.

De los interfaces también se hereda, aunque se suele utilizar más el término implementa
por la palabra reservada utilizada: implements.

public interface MiInterface { public class MiClase implements MiInterface {

… …

} }

Ejemplo:

public interface Comuncacion {


void saludo(); //método abstracto
void despedida(); //método abstracto
}

La clase Gerente implementa la interfaz Comunicacion.

public class Gerente implements Comunicacion{


public void saludo() {
System.out.println (“Buenos días”);
}
public void despedida() {
System.out.println (“Buenas noches”);
}
}

41 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Consideraciones importantes:

Una clase puede implementar múltiples interfaces consiguiendo así la


herencia múltiple. Y también un interface puede heredar de otros interfaces.

Una clase puede heredar de otra clase (como máximo de una) y a la vez heredar
de múltiples interfaces.

Si una clase implementa una interface y no implementa todos sus métodos deberá
ser definida como abstracta.

42 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Interface versus Clases abstractas

Un interface es una construcción similar a una clase abstracta en Java, pero con las
siguientes diferencias:

1. Para declarar una interfaz se utiliza la palabra clave interface en lugar de class o
abstract class.:

public interface NombreInterfaz {

....

43 - 130
Tema 12. Programación orientada a objetos: polimorfismo

2. Todo método de la interface es abstracto y público sin necesidad de


declararlo, es decir, no hace falta poner abstract public porque por defecto todos los
métodos son abstract public.

Por lo tanto un interface en Java no implementa ninguno de los métodos que declara:
ninguno de sus métodos tiene cuerpo.

En la interface todos los métodos son abstractos, en una clase abstracta no


necesariamente todos los métodos son abstractos.

En la interface se especifica qué se debe hacer pero no su implementación. Serán las


clases que implementen estas interfaces las que describan la lógica del
comportamiento de los métodos.

3. Las interfaces no tienen ningún constructor.

Las interfaces solo pueden declarar constantes, las clases abstractas pueden declarar
datos constantes y no constantes.

4. Un interfaz solo admite campos de tipo “public static final”, es decir,


campos de clase (static) , públicos y constantes.

No hace falta incluir las palabras public static final porque todos los campos serán
tratados como si llevaran estas palabras. Recordemos que static equivalía a “de clase” y
final a “constante”.

Las interfaces pueden ser un lugar interesante para declarar constantes que van a ser
usadas por diferentes clases en nuestros programas.

5. Una clase puede derivar de un interface de la misma manera en que puede derivar de
otra clase. No obstante, se dice que el interface se implementa (implements),
no se extiende (extends) por sus subclases. Por tanto para declarar la “herencia” de
un interface se usa la palabra clave implements en lugar de extends.

Las interfaces se implementan, las clases abstractas se heredan (Java no permite la


herencia múltiple).

44 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Una clase puede implementar uno o varios interfaces.

Una clase puede implementar uno o varios interfaces en Java, pero sólo puede extender
una clase. Implementar varios interfaces en una sola clase es lo más parecido que tiene
Java a la herencia múltiple.

Diremos que una interfaz en Java define un tipo cuyos métodos están todos sin
implementar y que resulta equivalente a una herencia múltiple (de clases abstractas) en
una clase. Si una clase implementa una interface, puede suceder:

a) Que implemente los métodos de la interface sobrescribiéndolos (una clase


concreta).

b) Que no implemente los métodos de la interface: obligatoriamente será una clase


abstracta y obligatoriamente ha de llevar la palabra clave abstract en su
encabezado para así indicarlo.

Ejemplo 6. Consideremos el siguiente diagrama de clases:

45 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Define las clases e interfaces del esquema anterior.

public interface Actor {…}


define la interfaz Actor

public abstract class Persona implements Actor {…}


define la clase abstracta Persona como implementación de la interfaz Actor.

public class Profesor extends Persona{…}


define la clase Profesor como extensión de la clase Persona.

public class Estudiante extends Persona{…}


define la clase Estudiante como extensión de la clase Persona.

public interface ObjetoInerte {…}


define la interfaz ObjetoInerte.

public class Vehiculo implements Actor, ObjetoInerte {…}:


define que la clase Vehiculo implementa a dos interfaces, la interfaz Actor y la
interfaz ObjetoInerte, es decir, que un vehículo es a la vez Actor y ObjetoInerte.

public class Coche extends Vehiculo {…}


define la clase Coche como extensión de la clase Vehiculo.

public class Bicicleta extends Vehiculo {…}


define la clase Bicicleta como extensión de la clase Vehiculo.

46 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9 Ejemplo práctico de creación y uso de interfaces: cuentasPorPagar

La empresa quiere utilizar una sola aplicación de cuentas por pagar, para calcular los
ingresos de nómina que deben pagarse a cada empleado y que sirva también para
calcular el pago vencido en las facturas (por los bienes o servicios comprados).

Son operaciones (los pagos de los empleados y el pago de las facturas) que tienen que
ver con el cálculo de un importe o monto a pagar a los empleados o a los proveedores.
Para un empleado, el pago se refiere a sus ingresos. Para una factura, el pago se refiere
al coste total de los bienes o servicios listados en la factura.

¿Podemos calcular esas cosas distintas, como los pagos para los empleados y las
facturas, en forma polimórfica en una sola aplicación?

¿Ofrece Java una herramienta que permita que las clases no relacionadas implementen
un conjunto de métodos comunes (por ejemplo, un método que calcule un monto a
pagar)?

Las interfaces de Java ofrecen exactamente esta herramienta.

La interfaz PorPagar describe la funcionalidad de cualquier objeto que deba ser capaz de
calcular un pago y, por lo tanto, debe ofrecer un método para determinar el importe
(monto) de pago.

La declaración de una interfaz empieza con la palabra clave interface y sólo puede
contener constantes y métodos abstract.

A diferencia de las clases, todos los miembros de la interfaz deben ser public, y las
interfaces no pueden especificar ningún detalle de implementación, como las
declaraciones de métodos concretos y variables de instancia. Por lo tanto, todos los
métodos que se declaran en una interfaz son public abstract de manera implícita, y todos
los campos son implícitamente public, static y final.

Buena práctica de programación


Las palabras reservadas public y abstract no se utilizan en la declaración de los
métodos porque son redundantes. De manera similar, las constantes deben
declararse sin las palabras claves public, static y final, ya que también son
redundantes.

47 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Para utilizar una interfaz, una clase debe especificar que implementa (implements) a esa
interfaz y debe declarar cada uno de sus métodos con la firma especificada en la
declaración de la interfaz. Una clase que no implementa a todos los métodos de la interfaz
es una clase abstracta, y debe declararse como abstract.

Implementar una interfaz es como firmar un contrato con el compilador


que diga, “Declararé todos los métodos especificados por la interfaz, o declararé mi clase
como abstract”.

Por lo general, una interfaz se utiliza cuando clases dispares (es decir, no relacionadas
mediante herencia) necesitan compartir métodos y constantes comunes. Esto permite
que los objetos de clases no relacionadas se procesen en forma
polimórfica; los objetos de clases que implementan la misma interfaz pueden
responder a las mismas llamadas a métodos.

A menudo, una interfaz se utiliza en vez de una clase abstract cuando no hay una
implementación predeterminada que heredar; esto es, no hay campos ni
implementaciones de métodos que heredar.

Puedes crear una interfaz que describa la funcionalidad deseada y después implementar
esta interfaz en cualquier clase que requiera esa funcionalidad.

48 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9.1 Desarrollo de una jerarquía PorPagar

La interfaz Porpagar contiene el método obtenerMontoPago, que devuelve un double


(importe) que debe pagarse para un objeto de cualquier clase que implemente a la
interfaz.

El método obtenerMontoPago es una versión de propósito general del método ingresos de


la jerarquía de Empleado.

Recomendación:
Al declarar un método en una interfaz, selecciona un nombre para el método que
describa su propósito en forma general, ya que el método podría implementarse
por muchas clases no relacionadas.

El diagrama de clases de UML muestra la jerarquía utilizada en nuestra aplicación de


cuentas por pagar.

La jerarquía comienza con la interfaz PorPagar. UML diferencia a una interfaz de otras
clases colocando la palabra “interface” por encima del nombre de la interfaz.

49 - 130
Tema 12. Programación orientada a objetos: polimorfismo

UML expresa la relación entre una clase y una interfaz a través de una relación conocida
como realización. Se dice que una clase “realiza”, o implementa, los
métodos de una interfaz. Un diagrama de clases modela una realización como una
flecha punteada que parte de la clase que realizará la implementación, hasta la interfaz.

El diagrama indica que cada una de las clases Factura y Empleado pueden realizar (es
decir, implementar) la interfaz PorPagar.

La clase Empleado es una clase abstracta.

La clase concreta EmpleadoAsalariado extiende a Empleado y hereda la relación de


realización de su superclase con la interfaz PorPagar.

50 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9.2 Declaración de la interfaz PorPagar

La interfaz PorPagar contiene el método public abstract obtenerMontoPago.

La interfaz PorPagar sólo tiene un método; las interfaces pueden tener cualquier número
de métodos.

El método obtenerMontoPago no tiene parámetros, pero los métodos de las interfaces


pueden tener parámetros.

// PorPagar.java
// Declaración de la interfaz PorPagar.
public interface PorPagar {
double obtenerMontoPago(); // calcula el pago; no hay implementación
}

Después de declarar la interfaz PorPagar:


- Declarar la nueva clase Factura, la cual implementa a la interfaz PorPagar.
- Modificar la clase Empleado de tal forma que también implemente a la interfaz
PorPagar.
- Modificar la subclase EmpleadoAsalariado para “ajustarla” en la jerarquía de
PorPagar (es decir, cambiaremos el nombre del método ingresos de
EmpleadoAsalariado por el de obtenerMontoPago).

51 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9. 3 Creación de la clase Factura

Crea la clase Factura para representar una factura.

La clase declara las variables de instancia private:


numeroPieza (Stirng),
descripcionPieza (String),
cantidad (int)
precioPorArticulo (double)

Las cuales indican el número de pieza, su descripción, la cantidad de piezas y el precio


por artículo.

La clase Factura también contiene un constructor, métodos obtener y establecer que


manipulan las variables de instancia de la clase y un método toString que devuelve una
representación String de un objeto Factura.

Los métodos setCantidad y setPrecioPorArticulo aseguran que cantidad y


precioPorArticulo sean sólo valores positivos.

La clase Factura implementa a la interfaz PorPagar.

public class Factura implements PorPagar

Recuerda: el contrato de interfaz con el compilador de la clase Factura obliga a


implementar el método obtenerMontoPago.

La clase Factura implementa el método de la interfaz PorPagar. El método


obtenerMontoPago calcula el pago total requerido para pagar la factura. El método
multiplica los valores de cantidad y precioPorArticulo y devuelve el resultado.

52 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9. 4 Modificación de la clase Empleado para implementar la interfaz PorPagar

Modificar la clase Empleado para que implemente la interfaz PorPagar, dos cosas:

En primer lugar, indica que la clase Empleado ahora implementa a la interfaz PorPagar.

En segundo lugar, como Empleado ahora implementa a la interfaz PorPagar, debemos


cambiar el nombre de ingresos por el de obtenerMontoPago en toda la jerarquía de
Empleado.

El método ingresos en la versión anterior de la clase Empleado se declaro como


abstracto, por esta razón la clase Empleado tuvo que declararse como abstract. Esto
obliga a cada clase derivada de Empleado a redefinir ingresos con una implementación
concreta.

Igualmente no tiene sentido implementar el método obtenerMontoPago en la clase


Empleado, ya que no podemos calcular el pago de los ingresos para un Empleado
general; primero debemos conocer el tipo específico de Empleado.

Recuerda que cuando una clase implementa a una interfaz, hace un contrato con el
compilador, en el que se establece que la clase implementará cada uno de los métodos
de la interfaz, o de lo contrario la clase se declara como abstract. Si se elige la última
opción, no necesitamos declarar los métodos de la interfaz como abstract en la clase
abstracta; ya están declarados como tales de manera implícita en la interfaz. Cualquier
subclase concreta de la clase abstracta debe implementar a los métodos de la interfaz
para cumplir con el contrato de la superclases con el compilador. Si la subclase no lo
hace, también debe declararse como abstract.

La clase Empleado no implementa al método obtenerMontoPago, por lo que la clase se


declara como abstract. Cada subclase directa de Empleado hereda el contrato de la
superclase para implementar el método obtenerMontoPago y, por ende, debe implementar
este método para convertirse en una clase concreta.

53 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9.5 Modificación de la clase EmpleadoAsalariado para usarla en la jerarquía PorPagar

La clase EmpleadoAsalariado, extiende a Empleado y debe, entonces, cumplir el


contrato de la superclase Empleado para implementar el método obtenerMontoPago de la
interfaz PorPagar.

Esta versión de EmpleadoAsalariado implementa al método obtenerMontoPago en vez


del método ingresos. Los dos métodos contienen la misma funcionalidad, pero tienen
distintos nombres.

El resto de las subclases de Empleado (EmpleadoPorHoras, EmpleadoPorComision y


EmpleadoBaseMasComision) también deben modificarse para que contengan el método
obtenerMontoPago en vez de ingresos, y así reflejar el hecho de que ahora Empleado
implementa a PorPagar.

Modela las clases de la jerarquía PorPagar utilizando UML.

54 - 130
Tema 12. Programación orientada a objetos: polimorfismo

9.6 Uso de la interfaz PorPagar para procesar objetos Factura y Empleado mediante el
polimorfismo.

Cuando una clase implementa a una interfaz, se aplica la misma relación “es un” que
proporciona la herencia.

Por ejemplo, la clase Empleado implementa a PorPagar, por lo que podemos decir que un
objeto Empleado es un objeto PorPagar. De hecho, los objetos de cualquier clase que
extienda a Empleado son también objetos PorPagar. Por ejemplo, los objetos
EmpleadoAsalariado son objetos PorPagar.

Al igual que con las relaciones de herencia, un objeto de una clase que implemente a una
interfaz puede considerarse como un objeto del tipo de la interfaz. Los objetos de
cualquier subclase de la clase que implementa a la interfaz también pueden considerarse
como objetos del tipo de la interfaz.

Así como podemos asignar la referencia de un objeto EmpleadoAsalariado a una variable


de la superclase Empleado, también podemos asignar la referencia de un objeto
EmpleadoAsalariado a una variable de la interfaz PorPagar.

Factura implementa a PorPagar, por lo que un objeto Factura también es un objeto


PorPagar, y podemos asignar la referencia de un objeto Factura a una variable PorPagar.

Observación de ingeniería de software


La herencia y las interfaces son similares en cuanto a su
implementación de la relación “es un”. Un objeto de una clase que
implementa a una interfaz puede considerarse como un objeto del
tipo de esa interfaz. Un objeto de cualquier subclase de una clase
que implemente a una interfaz también puede considerarse como
un objeto del tipo de la interfaz.

Crea la clase PruebaInterfazPorPagar para procesar un conjunto de objetos Factura y


Empleado en forma polimórfica en una sola aplicación.

Define un array de cuatro variables PorPagar. Asignan las referencias de objetos Factura
a los primeros dos elementos, asignan las referencias de objetos EmpleadoAsalariado a
los dos siguiente elementos del array.

55 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Estas asignaciones se permiten debido a que un objeto Factura es un objeto PorPagar, un


EmpleadoAsalariado es un Empleado, y un Empleado es un objeto PorPagar.

Utiliza una instrucción for mejorada para procesar cada objeto PorPagar de manera
polimórfica, imprimiendo en pantalla el objeto como un String, junto con el pago vencido.

PorPagar objetosPorPagar[] = new PorPagar[ 4 ];


objetosPorPagar[ 0 ] = new Factura( ….. );
objetosPorPagar[ 1 ] = new Factura(..... );
objetosPorPagar[ 2 ] = new EmpleadoAsalariado( ….. );
objetosPorPagar[ 3 ] = new EmpleadoAsalariado( ... );

// procesa en forma genérica cada elemento en el arreglo objetosPorPagar


for ( PorPagar porPagarActual : objetosPorPagar ) {
// imprime porPagarActual y su monto de pago apropiado
….
….

56 - 130
Tema 12. Programación orientada a objetos: polimorfismo

10 Declaración de constantes con interfaces

De manera implícita, los campos (atributos) de una interfaze son public, static y
final; aunque no se especifiquen estas palabras palabras en la declaración.

En una interfaz se pueden declarar un conjunto de constantes que pueden utilizarse en


muchas declaraciones de clases.

Ejemplo interfaz Constantes:


public interface Constantes {
int UNO = 1;
int DOS = 2;
int TRES = 3;
}

Una clase puede usar estas constantes, para lo cual importa la interfaz y después hace
referencia a cada constante como

Constantes.UNO
Constantes.DOS
Constantes.TRES.

Observaciones:
Una clase puede hacer referencia a las constantes importadas sólo con sus nombres (es
decir, UNO, DOS y TRES) si utiliza una declaración static import para importar la interfaz.

import static nombrePaquete.NombreClase.nombreMiembroEstático;

import static nombrePaquete.NombreClase.*;

A partir de Java SE 5.0, se pueden crear crear conjuntos de constantes como


enumeraciones con la palabra clave enum.

http://puntocomnoesunlenguaje.blogspot.com.es/2013/04/java-enum- enumerados-en-
java.html

57 - 130
Tema 12. Programación orientada a objetos: polimorfismo

11. Interfaces comunes de la API de Java

Una breve descripción de algunas de las interfaces que se encuentran en la API de Java.
El poder y la flexibilidad de las interfaces se utilizan con frecuencia a lo largo de la API de
Java.

Estas interfaces se implementan y usan de la misma forma que las interfaces que crea un
programador (la interface PorPagar). Las interfaces de la API de Java permiten utilizar
clases propias dentro de los marcos de trabajo que proporciona Java, como el comparar
objetos de sus propios tipos y crear tareas que se ejecuten de manera concurrente con
otras tareas en el mismo programa.

Interface Descripción
Comparable Java contiene varios operadores de comparación (<, <=, >, >=, ==, !=) que nos permiten
comparar valores primitivos. Sin embargo, estos operadores no se pueden utilizar para
comparar el contenido de los objetos.
La interface Comparable se utiliza para permitir que los objetos de una clase que implementa
a la interface se comparen entre sí.
La interface contiene un método, compareTo, que compara el objeto que llama al método con
el objeto que se pasa como argumento para el método.
Las clases deben implementar el método compareTo de tal forma que devuelva un valor
indicando si el objeto en el cual se invoca es menor (valor de retorno entero negativo), igual
(valor de retorno 0) o mayor (valor de retorno entero positivo) que el objeto que se pasa
como argumento, utilizando cualquier criterio especificado por el programador. (Por ejemplo,
si la clase Empleado implementa a Comparable, su método compareTo podría comparar
objetos Empleado en base a sus montos de ingresos.)
La interface Comparable se utiliza comúnmente para ordenar objetos en una colección como
un array.
Serializable Una interface que se utiliza para identificar clases cuyos objetos pueden escribirse en
(serializarse), o leerse desde (deserializarse) algún tipo de almacenamiento (archivo en disco,
campo de base de datos) o transmitirse a través de una red.
Uno de sus usos es con Archivos y flujos y Redes.
Runnable La implementa cualquier clase para la cual sus objetos deban poder ejecutarse en paralelo,
usando una técnica llamada subprocesamiento múltiple.
La interface contiene el método run que describe el comportamiento de un objeto al
ejecutarse.

Interfaces de Cuando escribe una dirección de un sitio Web o cuando hace clic en un botón en el
escucha navegador Web, éste debe responder a su interacción y realizar la tarea que se le indica.
de eventos de la GUI La interacción se conoce como evento, y el código que utiliza el navegador para responder a
un evento se conoce como manejador de eventos.
Se crean GUIs y los manejadores de eventos para responder a las interacciones del usuario.
Los manejadores de eventos se declaran en clases que implementan una interface de escucha
de eventos apropiada. Cada interface de escucha de eventos especifica uno o más métodos
que deben implementarse para responder a las interacciones de los usuarios.

SwingConstants Contiene un conjunto de constantes que se utilizan en la programación de GUI para


posicionar los elementos de la GUI en la pantalla.

58 - 130
Tema 12. Programación orientada a objetos: polimorfismo

12 Interface List

Esta interface es la encargada de agrupar una colección de elementos en forma de lista,


es decir, uno detrás de otro. En una lista los elementos pueden ser accedidos por un
índice que indica la posición del elemento en la colección.

Esta secuencia de elementos en forma de lista (al igual que los arrays) está “basada en
0”. Esto quiere decir que el primer elemento no es el que está en la posición “1”, sino en la
posición “0”.

Debido a la gran variedad y tipo de listas que puede haber con distintas características
como permitir que contengan o no elementos null, o que tengan restricciones en los tipos
de sus elementos, hay una gran cantidad de clases que implementan esta interfaz.

Algunas de las clases que implementan la interface List:

– ArrayList

– LinkedList

– Stack

– Vector

– …....

Al definir interfaces permitimos la existencia de variables polimórficas y la invocación


polimórfica de métodos, como ya hemos visto con la clase Factura y la clase Empleado.

Por ejemplo: el hecho de declarar una variable de tipo lista, por ejemplo

List <String> miLista;

nos dice que miLista va a ser una variable de tipo List, pero todavía no hemos definido
cuál de las posibles implementaciones va a ser, por ejemplo LinkedList, ArrayList u otras.

59 - 130
Tema 12. Programación orientada a objetos: polimorfismo

El código podría definir que se implementara de una u otra manera en función de las
circunstancias usando condicionales. O a nivel de programación, mantendríamos la
definición como List y nos permitiría comprobar el rendimiento de distintas configuraciones
(hacer funcionar miLista bien como ArrayList o bien como LinkedList viendo su
rendimiento).

La variable declarada se crea cuando escribimos

miLista = new LinkedList <String> ();


miLista = new ArrayList <String>();

o también se puede usar la sintaxis en su definición:

List <String> miLista = new LinkedList <String> ();


List <String> miLista = new ArrayList <String>();

Usar una u otra implementación puede dar lugar a diferentes rendimientos de un


programa. ArrayList responde muy bien para la búsqueda de elementos situados en
posiciones intermedias pero la inserción o eliminación de elementos puede ser más rápida
con una LinkedList. Declarando las variables simplemente como List tendremos la
posibilidad de que nuestro programa pase de usar un tipo de lista a otro tipo.

MiLista → actúa como una variable polimórfica.

60 - 130
Tema 12. Programación orientada a objetos: polimorfismo

13 La clase ArrayList

ArrayList como su nombre indica basa la implementación de la lista (interface list) en un


array. Eso sí, un array especial:

– Dinámico en tamaño (es decir, de tamaño variable), pudiendo agrandarse el


número de elementos o disminuirse.
– Cada elemento es un objeto (una lista de objetos).
– Implementa todos los métodos de la interfaz List y otras interfaces; y permite
incluir elementos nulos.

La clase ArrayList implementa varias interface y extiende la clase abstracta AbstractList:

public class ArrayList


extends AbstractList
implements List, RandomAccess, Cloneable, Serializable

http://docs.oracle.com/javase/7/docs/api/

Java utiliza clases abstractas en el API de la misma forma que podemos nosotros usarlas
en nuestros programas. La clase AbstractList del paquete java.util es una clase abstracta
con tres subclases:

Más información: http://docs.oracle.com/javase/7/docs/api/

61 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La clase ArrayList pertenece a la biblioteca java.util. Por tanto, para emplearla en nuestras
clases o programas debemos importa la clase o el paquete

import java.util.ArrayList
(o import java.util.*).

La clase ArrayList tiene métodos como, por ejemplo, para añadir un objeto en una
posición determinada, añadir un objeto al final de la lista, recuperar un objeto situado en
determinada posición, etc.

Los objetos de un ArrayList tienen un orden, que es el orden en el que se insertaron en la


lista.

Un aspecto a tener muy presente: hablamos de colecciones de objetos. Por tanto, un


ArrayList no puede ser, por ejemplo una lista de enteros como tipo primitivo (int) pero sí de
objetos Integer.

Crear un ArrayList vacío

Si no especificamos el tipo de objeto que se va almacenar, un ArrayList en Java se crea


de la siguiente forma:

ArrayList array = new ArrayList();

Esta instrucción crea el ArrayList vacío llamado “array”, que puede contener object
(objetos de cualquier tipo).

62 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Un ArrayList de objects puede tener alguna complicación a la hora de trabajar con él,
necesidad, por ejemplo, de realizar casting. Por eso, una alternativa a esta declaración es
indicar el tipo de objetos que contiene. En este caso, el ArrayList solo podrá contener
objetos de ese tipo.

ArrayList <tipo> nombreArray = new ArrayList <tipo>();

<tipo> debe ser una clase. Indica el tipo de objetos que contendrá el ArrayList. No se
pueden usar tipos primitivos. Para un tipo primitivo se debe utilizar su clase envolvente.
Por ejemplo: crea el ArrayList “numeros” de números enteros.

creación del objeto de la clase ArrayList

ArrayList <Integer> numeros = new ArrayList<Integer>();

tipo de datos nombre del ArrayList

Se puede crear un ArrayList con un determinado tamaño, indicando en el constructor el


número de elementos que queremos que tenga :

ArrayList <String> nombres = new ArrayList<String>(3);

tamaño por defecto

También podremos crear un ArrayList desde los datos que contenga una colección, se
estudiará más adelante.

63 - 130
Tema 12. Programación orientada a objetos: polimorfismo

13.1 Métodos de ARRAYLIST

Algunos métodos que proporciona ArrayList son:

MÉTODO DESCRIPCIÓN

size() Devuelve el número de elementos (int)

add(X) Añade el objeto X al final. Devuelve true.

add(posición, X) Inserta el objeto X en la posición indicada.

get(posicion) Obtiene el elemento que está en la posición


indicada.

remove(posicion) Elimina el elemento que se encuentra en la


posición indicada. Devuelve el elemento eliminado.

remove(X) Elimina la primera ocurrencia del objeto X.


Devuelve true si el elemento está en la lista.

clear() Elimina todos los elementos.

set(posición, X) Sustituye el elemento que se encuentra en la


posición indicada por el objeto X. Devuelve el
elemento sustituido.

contains(X) Comprueba si la colección contiene al objeto X.


Devuelve true o false.

indexOf(X) Devuelve la posición del objeto X. Si no existe


devuelve -1

Puedes consultar todos los métodos en:

http://docs.oracle.com/javase/7/docs/api/java/util/ArrayList.html

64 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo 7

Si no especificamos el tipo de objeto que se va almacenar un ArrayList en Java se crea


de la siguiente forma:

ArrayList array = new ArrayList();

Esta instrucción crea el ArrayList “array” vacío que puede contener objetos de cualquier
tipo.

Crea un array con la edad de 10 personas adultas en edad laboral (18 a 65) y calcula su
media. Mostrar los números generados y la media:

Array[0]: 40
Array[1]: 23
Array[2]: 51
Array[3]: 25
Array[4]: 39
Array[5]: 41
Array[6]: 53
Array[7]: 36
Array[8]: 20
Array[9]: 57
Media: 38

Para introducir la edad vamos a utilizar la generación de un número aleatorio entero entre
18 y 65.

Los números enteros generados no son objetos. Son datos de tipos básicos (int) pero el
compilador los convierte automáticamente en objetos de su clase envolvente (clase
contenedora o wrapper ) antes de añadirlos al array.

65 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo8. Implementa y prueba el siguiente código:

ArrayList<String> nombres = new ArrayList<String>();


nombres.add("Ana");
nombres.add("Luisa");
nombres.add("Felipe");

// Escribe nombres: [Ana, Luisa, Felipe]


System.out.println(nombres.toString());

// Escribe cada elemento del array


System.out.println("\tnumeros[0]: " +nombres.get(0));
System.out.println("\tnumeros[1]: " +nombres.get(1));
System.out.println("\tnumeros[2]: " +nombres.get(2));

// inserta un elemento en el array


nombres.add(1, "Pablo");
System.out.println(nombres);// [Ana, Pablo, Luisa, Felipe]
System.out.println("\tnumeros[0]: " +nombres.get(0));
System.out.println("\tnumeros[1]: " +nombres.get(1));
System.out.println("\tnumeros[2]: " +nombres.get(2));
System.out.println("\tnumeros[3]: " +nombres.get(3));

// borrar un elemento del array


nombres.remove(0);
System.out.println(nombres);// [Pablo, Luisa, Felipe]
System.out.println("\tnumeros[0]: " +nombres.get(0));
System.out.println("\tnumeros[1]: " +nombres.get(1));
System.out.println("\tnumeros[2]: " +nombres.get(2));

// modifica el contenido de un elemento del array


nombres.set(0,"Alfonso");
System.out.println(nombres);// [Alfonso, Luisa, Felipe]
System.out.println("\tnumeros[0]: " +nombres.get(0));
System.out.println("\tnumeros[1]: " +nombres.get(1));
System.out.println("\tnumeros[2]: " +nombres.get(2));

// accede a un determinado elemento


String primero = nombres.get(0);
String ultimo = nombres.get(nombres.size() - 1);
System.out.println("primer elemento: "+primero + " - "
+ "último elemento: "+ ultimo);

//ordena los elementos del array


Collections.sort(nombres);
System.out.println(nombres);
System.out.println("\tnumeros[0]: " +nombres.get(0));
System.out.println("\tnumeros[1]: " +nombres.get(1));
System.out.println("\tnumeros[2]: " +nombres.get(2));

66 - 130
Tema 12. Programación orientada a objetos: polimorfismo

13.2 Recorrer un Arraylist

Suponemos un array definido y cargado previamente llamado numeros.

ArrayList <Integer> numeros = new ArrayList<Integer>();

Varias formas de recorrer un Arraylist:

a.-Podemos recorrerlo de forma clásica con un bucle for, por ejemplo:

for (int i = 0; i<numeros.size(); i++) {


System.out.println(numeros.get(i));
}

b.- Mediante “for mejorado”, por ejemplo: :

for(Integer i: numeros){
System.out.println(i);
}

i es una variable de tipo Integer.

- Si el array contiene objetos de tipos distintos o desconocemos el tipo:

for(Object objeto: nombreArray){


System.out.println(objeto);
}

c.- Utilizando un objeto Iterator.

http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html

La ventaja de utilizar un Iterador es que no necesitamos indicar el tipo de objetos que


contiene el array, ni manejar un índice.

67 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La interface Iterable está incluida en el api de Java :java.lang.Iterable.

Implementar Iterable obliga a sobreescribir un método que es iterator() que nos devuelve
un objeto de tipo Iterator que permite iterar o recorrer los elementos de una colección.

Todas las colecciones implementan Iterable. Implementar Iterable tan sólo obliga a sobreescribir
un método que es iterator()

El método iterator de la inteface Iterable:

Iterator iterator();

Iterator <tipo> iterator();

Devuelve un iterador sobre una colección, se puede espeficicar el tipo de


elementos de la colección.

Lo primero es tener claro que hay que distinguir el método iterator (en minúsculas,
método de la interface Interable) y el tipo definido en el api de Java Iterator (con
mayúsculas). Iterator con mayúsculas es un tipo definido por la Interface Iterator:
java.util.Iterator.

El método iterator() nos va a permitir obtener un objeto de tipo Iterator que representa la
colección a recorrer. Los métodos para los objetos de tipo Iterator nos van a permitir
operar con cada elemento de la colección.

Sus métodos son:

- boolean hasNext(); // Indica ( devuelve true) sí tiene más elementos desde la


posición en la que se encuentra el iterador.

- Object next(); // Devuelve el primer elemento y se queda apuntando al siguiente.


En la siguiente llamada, devolverá el segundo elemento y apuntará al siguiente y
así sucesivamente hasta el final de la colección que devolverá un null.

- void remove(); // Borra el elemento recuperado por el iterador.

68 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo:
// Se importan las clases ArrayLis e Iterator
import java.util.ArrayList;
import java.util.Iterator;

// se define un objeto de tipo ArrayList denominado numeros


ArrayList <Integer> numeros = new ArrayList <Integer>();

//se insertan elementos


numeros.add(new Integer(75));
numeros.add(new Integer(65));
numeros.add(new Integer(95));
numeros.add(new Integer(55));

//se recorre la colección numeros utilizando un iterator


Iterator it = numeros.iterator();//se crea el iterador it para la colección (ArrayList) numeros
while(it.hasNext()) //mientras queden elementos
System.out.println(it.next()); //se obtienen y se muestran

69 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo9. Prueba a recorrer el arrayList del ejemplo ejemplo7 utilizando las 3 formas de
recorrer un ArrayList.
El ejemplo se puede realizar utilizando un arrayList de objetos indefinidos llamado array

ArrayList array = new ArrayList();

O un arrayList de objetos Integer llamado array

ArrayList<Integer> array = new ArrayList<Integer>();

Es importante que entiendas la diferencia, observa lo que cambia.

Intenta obtener una ejecución parecida a la que se muestra a continuación:

Bucle for
array[0]: 20
array[1]: 43
array[2]: 56
array[3]: 57
array[4]: 40
array[5]: 25
array[6]: 32
array[7]: 64
array[8]: 27
array[9]: 40

Bucle for mejorado


array[0]: 20
array[1]: 43
array[2]: 56
array[3]: 57
array[4]: 40
array[5]: 25
array[6]: 32
array[7]: 64
array[8]: 27
array[9]: 40

Utilizando un objeto Iterator


array[0]: 20
array[1]: 43
array[2]: 56
array[3]: 57
array[4]: 40
array[5]: 25
array[6]: 32
array[7]: 64
array[8]: 27
array[9]: 40

70 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo10: La clase Persona tiene los atributos dni, nombre, apellido y edad (valor
positivo, 0 en caso contrario).

Crea una clase Test que guarde 5 objetos del tipo Persona en un arrayList.

ArrayList <Persona> array = new ArrayList <Persona>();

Persona p1=new Persona("777555888", "Pepe", "Lirios", 25);


array.add(p1);
Persona p2=new Persona("666555999", "Inés", "Vals", 58);
array.add(p2);
Persona p3=new Persona("222555888", "Santigo", "Catalán", 15);
array.add(p3);
Persona p4=new Persona("333555000", "Mario", "Fulano", 42);
array.add(p4);
Persona p5=new Persona("007555111", "Isabel", "Fernández", 17);
array.add(p5);

Recorre el arrayList e indica al finalizar el recorrido el nombre y el apellido de la persona


con más edad.

La persona de mayor edad es: Inés Vals

Prueba a recorre el array de las tres formas: Test1, Test2 y Test3

71 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo10Bis. En el ejemplo anterior hemos supuesto (muy mal supuesto) que no existen
dos personas con la misma edad, mejora el código teniendo en cuenta que pueden existir
personas en el array con la misma edad.
Muestra de la persona o personas con la máxima edad su nombre y apellido. Escribe
antes el número de personas que tienen la máxima edad.

Ejemplo:

Persona p1=new Persona("777555888", "Pepe", "Lirios", 25);


array.add(p1);
Persona p2=new Persona("666555999", "Inés", "Vals", 58);
array.add(p2);
Persona p3=new Persona("222555888", "Santigo", "Catalán", 58);
array.add(p3);
Persona p4=new Persona("333555000", "Mario", "Fulano", 54);
array.add(p4);
Persona p5=new Persona("007555111", "Isabel", "Fernández", 58);
array.add(p5);

Hay 3 personas con la mayor edad:


Inés Vals
Santiago Catalán
Isabel Fernández

72 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo11: Ordena el array anterior de objetos Persona por la edad, y muestra su


contenido ordenado.
Sobre-escribe el método toString en la clase Persona

Adaptamos el método de la burbuja al ArrayList de tipo Persona.

Algoritmo Burbuja // Ordenación ASCENDENTE


ArrayList <Persona> array = new ArrayList();
<Cargar array>
pas: variable numérica entera
ele: variable numérica entera
auxPersona: variable Persona

para (pas=1;pas<array.size();pas=pas+1)

para( ele=0;ele=< array.size()-pas-1;ele=ele+1)

si array.get[ele].getEdad()>array.get[ele+1].getEdad()
auxPersona=array.get[ele]
array.set([ele], array.get[ele+1])
array.set([ele+1],auxPersona)
fin_si

fin_para

fin_para
Fin_Algortimo

Otra forma de hacerlo es utilizar el método sort de la clase Collections

.Collections.sort(arrayPersona);

. Antes hay que especificar (implementar el método compareTo de la


interface Comparable) en la clase Empleado para indicar cuando un objeto
es mayor o igual a otro objeto de la clase.

73 - 130
Tema 12. Programación orientada a objetos: polimorfismo

14. Interface Comparable y método compareTo

Vamos a realizar un ejercicio acerca de cómo una clase debe hacer uso de la interfaz
Comparable.

Esto nos va a permitir que en dicha clase se pueda realizar la comparación de objetos,
permitiendo hacer ordenaciones de los mismos.

Implementar la interfaz Comparable en la clase Persona, y después sobreescribir el


método compareTo.

Al implementar la interfaz Comparable en la clase Persona estamos obligados a


sobreescribir el método compareTo, donde implementaremos los criterios de
comparación.

public class Persona implements Comparable <Persona> {


private String dni;
private String nombre;
private String apellido;
private int edad;
public Persona(String dni, String nombre, String apellido, int edad) {
this.dni = dni;
this.nombre = nombre;
this.apellido = apellido;
setEdad(edad);
}
...
public int compareTo(Persona p) {
. . .
}
}

74 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Según las especificaciones de la interface, el método debe devolver un número:


negativo
0
positivo
en función de que el objeto que comparamos con el objeto que pasamos como parámetro:
sea menor
sea igual
sea mayor
respectivamente.

Ejemplo:

Persona p1 = new Persona("777555888", "Pepe", "Lirios", 25);


Persona p2 = new Persona("111566777", "Inés", "Gómez", 25);

p1.compareTo(p2) devuelve:

p1 es menor: negativo
p1=p2: 0
p1 es mayor: positivo

Lo primero es determinar el criterio de cuando dos Personas son iguales, mayor o menor.
En nuestro ejemplo vamos a decir que un objeto persona es “más grande que otro” si
tiene la edad mayor, en cambio será menor si tiene una edad menor y serán iguales, si y
solo si, su edad es igual.

Fíjate que el criterio de ordenación lo hemos decidido nosotros. Una vez decidido, lo
implementaremos en forma de código. Distintos programadores pueden aplicar distintos
criterios a la hora de implementar el método compareTo.

En general este método será distinto para cada clase dependiendo de


cómo queramos comparar los objetos con los que estamos trabajando.

75 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Con los criterios elegidos, nuestro código para la clase Persona quedaría de la siguiente
manera:

public class Persona implements Comparable<Persona>{


. . .
public int compareTo(Persona per) {
int resultado;

if (this.edad<per.edad){
resultado = -1;

}else if (this.edad>per.edad) {
resultado = 1;
}else{
resultado=0;
}
return resultado;

}// fin método compareTo

Ejemplo11Comparable
Implementa la intefaz Comparable en la clase Persona.

Para probar lo hecho, vamos a codificar una clase “Programa”, que generar 2 Personas y
los compara mostrando cuál es la mayor.

public static void main(String arg[]) {


Persona p1 = new Persona("777555888", "Pepe", "Lirios", 28);
Persona p2 = new Persona("111566777", "Inés", "Gómez", 25);

if (p1.compareTo(p2) < 0 ) {
System.out.println("La persona p1: es menor."); }

else if(p1.compareTo(p2) > 0 ) {


System.out.println("La persona p1: es mayor."); }
else {
System.out.println ("La persona p1 es igual a
la persona p2");
}

}
}

76 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Esta implementación de la interfaz Comparable es muy habitual ya que en general


muchas veces vamos a desear que nuestras clases sean comparables no solo por el
hecho de poder comparar objetos de dicha clase, sino por el uso de poder ordenar objetos
de esa clase. La ordenación es una de las funciones más importantes y necesarias en el
ámbito de la informática.

Ejemplo11Bis: Ordena el array anterior de objetos Persona por la edad, y muestra su


contenido ordenado ascendentemente y descendentemente.

//Ordenación ascendente
Collections.sort(array);

//Ordenación descendente
Collections.sort(array, Collections.reverseOrder());

77 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejemplo12. Crea un array de 10 Personas e imprime las personas (número indeterminado


de personas) que tengan las 3 edades más pequeñas.

Las edades se pueden repetir, es decir, pueden existir varias personas con la mima edad.

Ejemplos de ejecuciones con diferentes valores de entrada

Persona [dni=777555888, nombre=Pepe, apellido=Lirios, edad=15]


Persona [dni=877955881, nombre=Antonio, apellido=Lirios, edad=15]
Persona [dni=222555888, nombre=Santiago, apellido=Catalán, edad=15]
Persona [dni=333555000, nombre=Mario, apellido=Fulano, edad=15]
Persona [dni=666555999, nombre=Inés, apellido=Vals, edad=17]
Persona [dni=007555111, nombre=Isabel, apellido=Fernández, edad=17]
Persona [dni=333555111, nombre=Mario, apellido=Félix, edad=17]
Persona [dni=117555111, nombre=María, apellido=Fernández, edad=18]

Persona [dni=777555888, nombre=Pepe, apellido=Lirios, edad=5]


Persona [dni=666555999, nombre=Inés, apellido=Vals, edad=6]
Persona [dni=222555888, nombre=Santiago, apellido=Catalán, edad=7]

Comprobar que la solución planteada vale para todos los casos, primero tendrás que
identificar cual puede ser la casuística posible para posteriormente comprobar su
funcionamiento.

78 - 130
Tema 12. Programación orientada a objetos: polimorfismo

COPIAR UN ARRAYLIST

El nombre de un ArrayList contiene la referencia al ArrayList, es decir, la


dirección de memoria donde se encuentra el ArrayList, igual que sucede con
los arrays estáticos.

Si disponemos de un ArrayList de enteros llamado ventas:

ArrayList<Integer> ventas = new ArrayList<Integer>();

La instrucción:
ArrayList<Integer> ventas1 = ventas;
No copia el array ventas en el nuevo array ventas1 sino que crea una nueva
referencia.

De esta forma tenemos dos formas de acceder al mismo ArrayList: mediante la


referencia ventas y mediante la referencia ventas1.

79 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Recuerda: ventas1 es el mismo ArrayList que ventas, no es una copia:

ArrayList <Integer> ventas = new ArrayList <Integer>();


ventas.add (new Integer(50));
ventas.add (new Integer(201));
ventas.add (new Integer(58));
ventas.add (new Integer(81));
ventas.add (new Integer(19));
ventas.add (new Integer(50));

//crea una nueva referencia a la colección ventas


ArrayList<Integer> ventas1 = ventas;
System.out.println("ventas : "+ventas);
System.out.println("ventas1: "+ventas1);

//modificamos la coleeción utilizando su referencia: ventas


ventas.set(0, new Integer(999));
System.out.println("ventas : "+ventas);
System.out.println("ventas1: "+ventas1);

ventas : [50, 201, 58, 81, 19, 50]


ventas1: [50, 201, 58, 81, 19, 50]
ventas : [999, 201, 58, 81, 19, 50]
ventas1: [999, 201, 58, 81, 19, 50]

Para hacer una copia podemos hacerlo:


- de forma manual elemento a elemento
- se puede pasar la referencia del ArrayList original al constructor del
nuevo:

ArrayList <Integer> ventas1 = new ArrayList<Integer>(ventas);

Ejercicio: prueba las dos formas de copiar ventas en un nuevo ArrayList


denominado ventas1. Comprueba modificando un elemento de una colección,
que ventas y ventas1 son dos ArrayList distintos.

80 - 130
Tema 12. Programación orientada a objetos: polimorfismo

15. Clases envolventes o wrappers

Java es un lenguaje de programación orientado a objetos. Un programa Java debe


contener objetos y las operaciones entre ellos. La única excepción son los tipos de datos
primitivos (int, double, char, etc.).

Los tipos de datos primitivos no son objetos pero en ocasiones es necesario tratarlos
como tales. Por ejemplo, hay determinadas clases que manipulan objetos (ArrayList, …).

Una forma de conseguir esto es utilizar un Wrapper, esto es un objeto cuya variable
miembro es el tipo primitivo. Es decir se encapsula al tipo primitivo en una clase.

Estas clases equiparan en funcionalidad a los tipos primitivos con el resto de clases y
además les proporcionan métodos para realizar otras tareas como conversión con
cadenas de caracteres en uno y otro sentido.

Existe una clase Wrapper para cada uno de los tipos primitivos, esto es, existen las
clases Byte, Short, Integer, Long, Float, Double, Boolean y Char (los nombres de las
clases wrapper empiezan por mayúscula, siguiendo la nomenclatura típica de Java).

Lo wrapper numéricos Byte, Short, Integer, Long, Float, Double son subclases de la clase
abstracta Number1.

1 Hay otras cuatro subclases de Number que no se discuten aquí. BigDecimal y BigInteger se usan
para cálculos de alta precisión. AtomicInteger y AtomicLong se usan para aplicaciones multihilo.

81 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Cada tipo primitivo tiene su correspondiente clase envolvente:

Tipo primitivo Clase Envolvente

byte Byte

short Short

int Integer

long Long

float Float

double Double

char Character

boolean Boolean

Las conversiones entre los tipos primitivos y sus clases envolventes son automáticas (no
es necesario hacer un casting).

• Boxing: Convertir un tipo primitivo en su clase Wrapper.


• Unboxing: Convertir un objeto de una clase Wrapper en su tipo primitivo.

Ejemplo de Boxing:
double x = 29.95;
Double y;
y = x; // auto-boxing
y=new Double(x); // sin auto-boxing

Ejemplo de Unboxing:
double x;
Double y = 29.95;
x = y; // auto-unboxing

x=y.doubleValue(); // sin auto-unboxing

82 - 130
Tema 12. Programación orientada a objetos: polimorfismo

15.1 La clase Number

La clase Number tiene métodos abstractos, que se agrupan en:

1. Métodos para obtener el valor del objeto en cada uno de los diferentes formatos
numéricos primitivos (byte, short, int..), que convierten el contenido del objeto this en el
valor primitivo.

byte byteValue()
short shortValue()
int intValue() Convierte el valor de este objeto Number al tipo de
long longValue() datos primitivo devuelto.
float floatValue()
double doubleValue()

Ejemplos:
Integer i1; // i1 es un objeto (referencia a un objeto)
int i2; // i2 es una variable primitiva de tipo
float f1;
i2= i1.intValue();
f1= i1.floatValue();

2. Método de comparación por igualdad. Determina si el contenido del objeto this es igual
al pasado como argument.

boolean equals(Object obj) Determina si este objeto numérico es igual al


argumento.
El método devuelve true si el argumento no es
null y es un objeto del mismo tipo y con el
mismo valor numérico que el objeto this

83 - 130
Tema 12. Programación orientada a objetos: polimorfismo

3. Métodos de comparación. Compara el objeto this con el pasado como argumento.

int compareTo(Byte anotherByte) Devuelven:


0 sin lo valores son iguales
int compareTo(Double anotherDouble) negativo si el objeto invocante es
menor
int compareTo(Float anotherFloat) Positivo si el objeto invocante es
mayor
int compareTo(Integer anotherInteger)

int compareTo(Long anotherLong)

int compareTo(Short anotherShort)

Integer i1= new Integer(45);


Integer i3= new Integer(50);
i1.compareTo(i3);

Prueba el siguiente ejemplo:

public class PruebaWrappersComparacion {

public static void main(String[] args) {


Integer iInt=new Integer(35);// Integer iInt=35
Integer jInt=new Integer(35);
//Comparación por igualdad incorrecta
System.out.println("Valores de iInt y jInt"+iInt+", "+jInt);
if (iInt==jInt)
System.out.println("Son iguales");
else
System.out.println("Son distintos");
//Comparación por igualdad correcta
if (iInt.equals(jInt))
System.out.println("Son iguales");
else
System.out.println("Son distintos");

//Comparación correcta
if (iInt.compareTo(jInt)==0)
System.out.println("Son iguales");
else
System.out.println("No son iguales");

84 - 130
Tema 12. Programación orientada a objetos: polimorfismo

15.2 Clase Integer

En la siguiente tabla aparecen algunos métodos de la clase Integer. El resto de clases


envolventes correspondientes a tipos primitivos numéricos (Byte, Short, Long, Float,
Double) tienen métodos similares.

Integer(int valor) Constructor a partir de un int


Integer n = new Integer(20);
Integer(String valor)
Constructor a partir de un String
String s = "123456";
Integer a = new Integer(s);

int intValue() Devuelve el valor equivalente


float floatValue() Integer n = new Integer(30);
double doubleValue() int x = n.intValue();
… double y = n.doubleValue();

int parseInt(String s) Método estático que devuelve un int a partir de un


String.
String s = "123456";
int z = Integer.parseInt(s);

String toBinaryString(int i) Métodos estáticos que devuelven un String con la


String toOctalString(int i) representación binaria, octal o hexadecimal del
String toHexString(int i) número.
int numero = 12;
String binario = Integer.toBinaryString(numero);

Integer valueOf(String s) Método Estático. Devuelve un Integer a partir de un


String.
Integer m = Integer.valueOf("123");

API de la clase Integer: http://docs.oracle.com/javase/7/docs/api/java/lang/Integer.html

85 - 130
Tema 12. Programación orientada a objetos: polimorfismo

15.3 Clase Character

Provee una serie de métodos para manipular los datos de tipo Character. En la siguiente
tabla aparecen algunos de estos métodos.

Character(char c) Constructor a partir de un char


char car = 'x';
Character a = new Character(car);

boolean isLowerCase(char ch) Comprueba si es un carácter en minúsculas.

boolean isUpperCase(char ch) Comprueba si es un carácter en mayúsculas.

boolean isDigit(char ch) Comprueba si es un dígito (carácter del 0 al 9).

boolean isLetter(char ch) Comprueba si es una letra.


Todos son estáticos.
if(Character.isUpperCase(c)){
.....
}

char toLowerCase(char ch) Devuelve el char en mayúsculas.

char toUpperCase(char ch) Devuelve el char en minúsculas.


Son estáticos.
char car = 'u';
System.out.println(Character.toUpperCase(car));

Ejemplo 13. Uso de la clase Character: programa que lee un texto


por teclado y muestra cuántos dígitos y letras contiene.

86 - 130
Tema 12. Programación orientada a objetos: polimorfismo

16 Resumen herencia-interfaz-polimorfismo.

“Aunque el polimorfismo es el mismo se aplique donde se aplique, el modo en


que se aplica desde una interfaz puede resultar un poco más oscuro y difícil de
entender” wikipedia.org

http://es.wikipedia.org/wiki/Polimorfismo_%28inform%C3%A1tica%29

16.1 Superclase – Interfaz, ¿qué puede contener?

Una superclase puede contener:


- Atributos (variables de instancia y constantes).
- Métodos concretos (con codificación)
- Métodos abstractos (solo la firma)

Una superclase debe declararse obligatoriamente abstracta si contiene métodos


abstractos.

Una interface puede contener:


- Constantes (variables miembro final)
- Métodos abstractos

87 - 130
Tema 12. Programación orientada a objetos: polimorfismo

16.2 Tipo de relación.

● Una subclase tiene una relación “es un” con la superclase que extiende.

● Una clase tiene una relación “es un” con la interface que implementa.

Se posibilita así, el tratamiento polimórfico usando variables que referencia a la


superclase (sea o no abstracta) o a la interface. Posibles casos:

1. Si una clase extiende (hereda) una superclase:


- Hereda todos sus miembros: variables y métodos.
- Son accesibles directamente los declarados en la superclase como public y
protected (en general, no es buena práctica declarar variables con el tipo de acceso
protected).
Recuerda: se declararan como public los métodos que representan el
comportamiento común que recoge la superclase)
- No son accesibles directamente los miembros prívate.
(Ingeniería de Software: se declararan como prívate las variables miembro de la
superclase. La subclase accede a ellos con los métodos public: set y get).
- Los métodos abstract de la superclase deben hacerse concretos en la subclase. Si
no la subclase debe declararse a su vez como abstract.
- Los métodos concretos de la superclase pueden ser sobreescritos (overriden) en
las subclases.

2. Si una clase implementa una interface:


- Tiene acceso a las constantes declaradas en la interface.
- La clase que implementa la interface debe hacer concretos todos los métodos de
la interface. Si alguno de los métodos no lo implementa la clase que implementa la
interface debe declararse abstract.

3. Si una subclase extiende una superclase que implementa una interface:


- Tiene acceso a las constantes declaradas en la interface.
- La subclase puede sobre-escribir tantos los métodos heredados propios de la
superclase o pertenecientes a las interfaces implementadas por la superclase.
- Si la superclase no hace concreto un método de la interface que implementa,
entonces la subclase debe hacerlo concreto (codificarlo) o debe declararse la
subclase como abstract.

88 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Las relaciones de tipo “es-un” y la sobre-escritura de métodos posibilitan el


tratamiento polimórfico.

16.3 Tratamiento Polimórfico

Consiste en crear variables (referencias) de una superclase o de una interface y utilizar estas
variables para apuntar a objetos de subclases (clases que extienden la superclase) o clases que
implementan la interface.

El polimorfismo se puede realizar utilizando variables de una SUPERCLASE o


variables de una INTERFACE. La relación “es un” con la la superclase que extiende o
con la interface que implementa permite el tratamiento polimorfico.

Superclase variableSuperclase = new Subclase(); Superclase


// La Subclase extiende la Superclase

Subclase

Interface variableInterface = new Clase();/ Interface


//Clase implementa la interface
Clase

Con la variable de la superclase o la interface, al invocar un método de la


superclase o interface, el procesador de Java detecta en tiempo de ejecución
cual es la clase real del objeto apuntado por la variable, e invoca al método de
la clase real del objeto (que estará siempre sobreescrito en el caso de la interface
o que puede estar sobreescrito en el caso de la herencia).

89 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Con la variable de la superclase o la interface, no se puede acceder a los métodos propios


(los que no hereda de la superclase ni implementa de la interface) de la clase real del objeto. En
estos casos:

- Para comprobar en tiempo de ejecución cual es la clase real del objeto apuntado por
referencia se utiliza el operador instanceof.
- Posteriormente ha de realizarse un casting.

// comprobar en tiempo de ejecución cual es la clase real del objeto


if ( variableSuperclase instanceof Subclase ){
Subclase variableSubclase = (Subclase) variableSuperclase; // casting
variableSubclase.nombreMetodo()
...
}

if ( variableInterface instanceof Clase ){


Clase variableClase = (Clase) variableInterface; // casting

}

90 - 130
Tema 12. Programación orientada a objetos: polimorfismo

I EJERCICIO “El carrito de la compra”

Versión 1.0

Una tienda virtual en Internet se dedica a la venta de productos de alimentación, ropa y electrónica.

Cada producto se caracteriza por un precio, un nombre y un código de barras. El precio del producto
debe ser positivo, si fuera negativo se inicializará con el valor 0.

Los productos de alimentación tienen a su vez una fecha de caducidad (atributo de tipo Calendar).

Los productos electrónicos tienen un plus adicional por tener todos 3 años de garantía.

Los productos de ropa tienen como característica el material en que han sido elaborados.

Cada venta que se realiza tiene un código de venta (número secuencial) y puede tener varios productos
(lista de productos). En esta versión no se tiene en cuenta las unidades de cada producto.

Se pide:

1. Realizar un diagrama UML donde se vean las relaciones entre clases, junto a los atributos y
métodos de cada una.

2. Implementar todas las clases con sus atributos y métodos.

3. Realiza la prueba de cada clase (pruebas de unidad). En sistemas complejos que incluyen
muchas clases es conveniente antes de realizar la aplicación probar todas las clases.

4. Implementar una aplicación CarritoDeLaCompra que recoja los productos correspondientes


a una venta y al final visualice la lista de la compra correspondiente a dicha venta. Una venta
puede tener uno o varios productos de alimentación, ropa y/o electrónica.

En esta versión se añaden productos a una venta (lista de productos de la venta), se deja para
otras versiones la posibilidad de eliminar productos de dicha lista.

91 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Propuesta de solución Versión 1.0

1. Realizar un diagrama UML donde se vean las relaciones entre clases, junto a los atributos y métodos
de cada una.

2. Implementar todas las clases con sus atributos y métodos.

Producto.java
Alimentacion.java (Ver Anexo Tratamiento de Fechas)
Electronica.java
Ropa.java
Venta.java

3. Prueba de cada clase

PruebaProducto.java
PruebaAlimentacion.java
PruebaElectronica.java
PruebaRopa.java
PruebaVenta.java

92 - 130
Tema 12. Programación orientada a objetos: polimorfismo

4. Implementar la aplicación CarritoDeLaCompra:

Recoge los productos correspondientes a una venta y al final visualice la lista de la compra
correspondiente a dicha venta. Una venta puede tener uno o varios productos de alimentación,
ropa y/o electrónica.

Algoritmo
instanciar objeto Venta
// escribir menú
escribir "1. Comprar Alimento"
escribir "2. Comprar Ropa"
escribir "3. Comprar Electrónica");
escribir "0. Finalizar Compra");
escribir "Elija opción: ");
leer tipoAccion
// entramos en el cuerpo del programa, salimos cuando ya no realizamos más compras
mientras (tipoAccion !=0)
//pedimos datos generales del producto
escribir "Introducir Código de Barras: "
leer codigoBarras
escribir “Introducir Nombre del producto: "
leer nombre
escribir "Introducir Precio del producto: "
leer precio
//pedimos datos concretos según el tipo de producto
según (tipoAccion)
caso1: escribir "Introducir Fecha Caducidad"
leer fecha (dia/mes/anyo)
instanciar objeto fecha
venta.incluirEnListaCompra(instanciar objeto Alimentacion)
// break
caso2: escribir "Introducir Material: "
leer material
venta.incluirEnListaCompra( instanciar objeto Ropa)
//break
caso3: escribir "Introducir Plus de Garantía: "
leer plusGarantia
venta.incluirEnListaCompra( instanciar objeto Electrónica)
fin_según
//escribir Menú y pedir opción
escribir "1. Comprar Alimento"
escribir "2. Comprar Ropa"
escribir "3. Comprar Electrónica");
escribir "0. Finalizar Compra");
escribir "Elija opción: ");
leer tipoAccion
fin_mientras
Escribir "Su lista de la compra es: ", codventa, listaCompra //método toString() del objeto Venta
Fin_Algoritmo

93 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Modifica el algoritmo para que se puedan hacer varias ventas, al finalizar la venta se muestra la lista de
compras y se consulta al usuario si desea hacer otra venta.

Anexo: Tratamiento de fechas

La clase Alimentación tiene un atributo de tipo Calendar, fecha de caducidad.

El método toString de la clase Alimentación deberá visualizar de forma correcta la fecha. Realiza las
pruebas correspondientes.

Fuente:

http://hopzone.com.es/KIMERAWEB/java/tema11.php

http://carloszuluaga.wikidot.com/articulos:manejo-de-fechas-en-java-ii

http://chuwiki.chuidiang.org/index.php?title=Clase_Calendar_de_Java

Clase Calendar

La clase Calendar surgió para cubrir las carencias de la clase Date en el tratamiento de fechas.

Creación de un objeto Calendar

Calendar es una clase abstracta, por lo que no podemos utilizar el operador new para crear objetos de la
misma.

Una subclase de Calendar representa una fecha de acuerdo a las reglas de un calendario específico.
GregorianCalendar es una subclase concreta de Calendar. La clase java.util.GregorianCalendar es una
implementación del calendario gregoriano que es usado en casi todo el mundo (es el que conocemos).

Futuras subclases podrían representar varios tipos de calendarios usados en diferentes lugares del
mundo.

Hora y fecha actual

A través del método estático getInstance() crearemos un objeto de una subclase de Calendar (gracias al
polimorfismo es totalmente transparente para el programador la clase GregorianCalendar, a la que
pertenece el objeto) que representa la hora y fechas actuales.

Calendar cal = Calendar.getInstance();

94 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Extraer los datos

Utilizando el método get() podemos recuperar de manera individual cada uno de los campos que
componen la fecha, indicando el campo concreto que se quiere obtener.

• public int get(int field)

Por ejemplo, mediante el siguiente programa se mostraría en pantalla la fecha actual:

import java.util.Calendar;

public class Ejemplo1 {

public static void main(String[] args) {


Calendar cal = Calendar.getInstance();
System.out.println("Día: "+ cal.get(Calendar.DAY_OF_MONTH) );
System.out.println();
System.out.println("Mes: "+ cal.get(Calendar.MONTH) );
System.out.println();
System.out.println("Año: "+ cal.get(Calendar.YEAR) );

El mes no cuenta desde el 1, sino desde el 0 (cero) así que hay que sumar 1 para una compresión lógica.

System.out.println("Mes: "+ (cal.get(Calendar.MONTH) + 1));

La clase Calendar tiene un único método get para obtener todos sus datos para lo cual se ayuda de una
serie de atributos constantes (static final int ) de la clase Calendar, los más importantes son:

1 YEAR: Año.
2 MONTH: Mes.
5 DATE, DAY_OF_MONTH: Día del mes.
7 DAY_OF_WEEK: Día de la semana entre 1 (SUNDAY) y 7
(SATURDAY).
10 HOUR: Hora antes o después del medio día (en intervalos de
12 horas).
11 HOUR_OF_DAY: Hora absoluta del día (en intervalos de 24 horas).
12 MINUTE: El minuto dentro de la hora.
13 SECOND: El segundo dentro del minuto.

Ejemplo2. Cambia el ejemplo anterior para obtener la fecha de hoy en el formato: “Mérida, a 27 de 03
de 2017”.

Cambiar una fecha

95 - 130
Tema 12. Programación orientada a objetos: polimorfismo

La fecha (cal) se puede cambiar con el método set(int atributo, int valor)

atributo es una de las constante mencionadas anteriormente (DAY_OF_MONTH, MONTH, YEAR, …).

Como la idea es que no nos sepamos que entero está asociado a que valor, la clase Calendar nos proporciona una
serie de constantes que nos ayudarán a encontrar dichos valores.

valor es la cantidad que se le quiere asignar.

Por ejemplo:

cal.set(Calendar.DAY_OF_MONTH, 11);
cal.set(Calendar.MONTH, 10);
cal.set(Calendar.YEAR, 2011);

¿Qué fecha obtenemos?

Los meses también se pueden referenciar con su literal (JANUARY, MARCH, JUNE, DECEMBER, ...)
por ejemplo

cal.set(Calendar.MONTH, Calendar.JANUARY)

Realiza un prueba (ejemplo3) que escriba el nombre de la localidad y la fecha de tu nacimiento.

GregorianCalendar

El ejemplo anterior se podría haber hecho creando un objeto del tipo GregorianCalendar con la fecha
requerida.

Esta clase es una clase concreta de Calendar y que por otro lado es el sistema de calendario estándar en
el mundo, al menos en el mundo occidental que comprende Europa, Norte, Centro y SurAmérica y
muchos otros países.

La clase GregorianCalendar tiene diversos constructores, ver API de Java:

http://docs.oracle.com/javase/7/docs/api/java/util/GregorianCalendar.html

96 - 130
Tema 12. Programación orientada a objetos: polimorfismo

A modo de ejemplo, algunos constructores de la clase GregorianCalendar:

GregorianCalendar(int year, int month, int dayOfMonth)

GregorianCalendar(int year, int month, int dayOfMonth, int hourOfDay,


int minute)

GregorianCalendar()

Ejemplos:

Calendar cal = new GregorianCalendar(1964,01,04);// 04-02-1964

Calendar calendario = new GregorianCalendar();

Si utilizamos el constructor: GregorianCalendar() ¿qué fecha se genera?

Pruébalo.

Calendar fecha = new GregorianCalendar();

fecha.get(Calendar.DAY_OF_MONTH)

fecha.get(Calendar.MONTH)

fecha.get(Calendar.YEAR)

fecha.get(Calendar.HOUR_OF_DAY )

fecha.get(Calendar.MINUTE )

fecha.get(Calendar.SECOND)

97 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Clase GregorianCalendar y operaciones

Realizar operaciones como sumar o restar días no es algo que dependa directamente de Calendar sino
más bien de una subclase de esta que implemente algún tipo de calendario.

GregorianCalendar es una subclase de Calendar y es la implementación directa del calendario


gregoriano tal y como lo conocemos hoy día.

Es con esta clase con la que podemos sumar 2, 3 ó 300 días a una fecha sin preocuparnos por
desbordamientos o recalcular meses o años, pues ella lo hace automáticamente tomando en cuenta las
reglas del calendario gregoriano.

De igual forma podemos obtener información como el día de la semana o la semana del año que fue
una fecha determinada.

Los métodos roll() y add()

Anteriormente vimos los métodos set() y get() de la clase Calendar para obtener fechas y los datos de
esas fechas, ahora veremos los métodos add() y roll() que nos permiten avanzar un tiempo exacto sobre
los datos obtenidos anteriormente.

El método add

add(int atributo, int valor)

atributo número entero o una de las constante mencionadas anteriormente (DAY_OF_MONTH, MONTH,
YEAR, HOUR_OF_DAY , MINUTE, SECOND,...)
valor es la cantidad que se le quiere sumar.

Por ejemplo agreguemos 3 días y 2 meses a la fecha actual:

Calendar cal = Calendar.getInstance();


System.out.println(cal.getTime());
cal.add(Calendar.DATE, 3);
cal.add(Calendar.MONTH, 2);
System.out.println(cal.getTime());

el método getTime() retorna un objeto de tipo java.util.Date.

Ejemplo4. Resta a la fecha actual 24 horas, 2 minutos y 45 segundos. Muestra la fecha actual y la nueva
fecha.

98 - 130
Tema 12. Programación orientada a objetos: polimorfismo

El método roll

roll(int atributo, int valor)

Esté método tiene un funcionamiento similar a add() excepto por que no modifica ningún otro
atributo, es decir, cuando el atributo dado por CONSTANTE llega a su límite inferior o superior
cambia al límite contrario pero sin modificar sus atributos siguientes.

Pruébalo:

Calendar cal = Calendar.getInstance();

System.out.println(cal.getTime());

cal.roll(Calendar.MONTH, 20);

System.out.println(cal.getTime());

Hemos visto el uso de Calendar y GregorianCalendar. Si trabajamos con Java 8 o superior, se


recomienda el uso de las clases incluidas dentro del paquete java.time para el manejo de tiempos y
fechas (clases como Clock, Duration, Instant, LocalDate, LocalTime, MonthDay, OffsetDateTime,
OffsetTime, Period, Year, YearMonth, ZonedDateTime, ZoneId y ZoneOffset).

Calcular la diferencia entre dos fechas

Si queremos calcular la diferencia entre dos fechas concretas, sin utilizar librerías externas, tenemos que
hacer el cálculo en milisegundos.
Calendar tiene un método getTimeInMilis() que nos devuelve el número de milisegundos que han
pasado desde el 1 de Enero de 1970 a las 00:00:00 hasta la fecha/hora representada por nuestra
instancia de Calendar. Si tenemos dos fecha/hora como Calendar, la diferencia entre ellas en
milisegundos se puede calcular fácilmente:

Calendar fecha1 = new GregorianCalendar(2016,00,25); // 25-01-2016


Calendar fecha2 = new GregorianCalendar(2017,02,28); // 28-03-2017
long milisegundos=fecha2.getTimeInMillis()-fecha1.getTimeInMillis();
System.out.println("Milisegundos: "+milisegundos);

El contenido de la variable diferencia es 36975600000.

99 - 130
Tema 12. Programación orientada a objetos: polimorfismo

A partir de aquí es fácil convertir esos milisegundos de diferencia a cualquier otra unidad que nos
interese, como número de días, de horas, etc. Por ejemplo, para pasar los milisegundos a días debemos
• dividir por 1000 para pasar los milisegundos a segundos
• después dividir por 60 para pasar los segundos a minutos
• después dividir por 60 para pasar los minutos a horas
• después dividir por 24 para pasar las horas a días
El siguiente código nos daría el número de días entre ambas fechas

long dias = milisegundos/(1000*60*60*24);


System.out.println("Días: "+dias);

Comparar fechas

Calendar implementa el método equals

if (fecha1.equals(fecha2))
System.out.println("Iguales");
else
System.out.println("Diferentes");

y el método compareTo
if (fecha1.compareTo(fecha2)==0)
System.out.println("Las fechas son iguales");
else if (fecha1.compareTo(fecha2)<0)
System.out.println("fecha1 es menor");
else
System.out.println("fecha1 es mayor");

100 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Versión 2.0 (Ampliación).

Crear la interface Valorable que declare el método float calcularImporte().

El método calcularImporte debe implementarse (codificar) en las clases:

– Producto.
El método debe devolver el importe del producto

– Venta.
El método debe calcular (devolver) la suma de los importes de todos los productos de la venta.

En las clases Alimentacion, Ropa y Electrónica evaluar si se debe sobre-escribir el método.

Realiza el diagrama UML correspondiente a la versión 2.0.

En la clase CarritoDeLaCompra, después de visualizar la lista de la compra correspondiente a una


venta, imprimir el importe total de dicha venta.

101 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Propuesta de solución Versión 2.0

public interface Valorable {


float calcularImporte();

public abstract class Producto implements Valorable{


...
@Override
public float calcularImporte() {
return precio;
}

102 - 130
Tema 12. Programación orientada a objetos: polimorfismo

public class Venta implements Valorable {

@Override
public float calcularImporte(){
float suma=0;
for(Valorable productoActual :listaCompra){
suma+=productoActual.calcularImporte();
}
return suma;
//también se podría realizar el tratamiento polimórfico con Producto
}
}

public class Electronica extends Producto{

….

@Override
public float calcularImporte(){
return super.calcularImporte()+plusGarantia;
}
}

103 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Versión 3.0 (Ampliación).

En las anteriores versiones cuando se desea comprar de un producto dos o más unidades se debe
incluir en la lista de la compra varias veces el producto.

En la versión 3.0 se deberá permitir a la hora de comprar un producto indicar el número de unidades
que se desean comprar.

Además, al finalizar la compra se visualizará el ticket de la compra con el siguiente formato:

Nombre de Producto Precio Unidades Importe


Lata de Tomate 1,80 5 9,00
Sudadera 14,50 2 29,00
Mp3 15,00 1 15,00
Importe 53,00

Realiza un diagrama UML, la creación y/o modificación de las clases para cumplir los nuevos
requerimientos.

A tener en cuenta:

Esto implica que se ha de definir una variable donde guardar el número de unidades a comprar
“unidadesVendidas”.

El número de unidades del producto que se desean comprar debe de pedirse al mismo tiempo que
pedimos los valores de los atributos del producto correspondiente.

System.out.print("Introducir Código de Barras: ");


codigoBarras=teclado.next();
System.out.print("Introducir nombre del producto: ");
nombre=teclado.next();
System.out.print("Introducir precio del producto: ");
precio= teclado.nextFloat();
System.out.print("Introducir unidades a comprar: ");
unidades=teclado.nextInt();

¿Cómo se implementaría en la aplicación?

La aplicación recoge una lista de productos (carrito de la compra) y después nos muestra un ticket con
los productos elegidos y el importe. Debemos por tanto, guardar el producto y las unidades vendidas
para poder obtener el ticket.

104 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Propuesta de solución Versión 3.0

Solución 1

Una solución sencilla es guardar la información en dos arrays (clase Venta):

private ArrayList<Producto> listaCompra = new ArrayList<Producto>();

private ArrayList<Integer> unidadesVendidas = new ArrayList<Intger>();

Se añadirán (clase Venta) los métodos para el tratamiento del arrayList de unidadesVendidas, y se
procesarían (se recorren) los dos array en paralelo.

Solución 2 (solución elegida).

Guardamos en un sólo arrayList el producto (objeto de tipo producto) y las unidades vendidas.

Creamos una nueva clase LineaDeVenta con dos atributos:

- productoVendido: Producto
- unidadesVendidas: int

con sus correspondientes métodos establecer y obtener.

La clase Venta contiene ahora una colección (arrayList) de objetos del tipo LineaDeVenta. Un objeto
LineaDeVenta tiene la información del producto y las unidades.

105 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Diagrama UML

106 - 130
Tema 12. Programación orientada a objetos: polimorfismo

LineaDeVenta

Clase que representa el producto comprado (objeto Producto) y las unidades.

public class LineaDeVenta implements Valorable{


//atributos
private Producto productoVendido;
private int unidadesVendidas;

//constructor
public LineaDeVenta(Producto productoVendido, int unidadesVendidas) {
this.productoVendido = productoVendido;
this.unidadesVendidas = unidadesVendidas;
}
// métodos establecer y obtener
public int getUnidadesVendidas() {
return unidadesVendidas;
}

public void setUnidadesVendidas(int unidadesVendidas) {


this.unidadesVendidas = unidadesVendidas;
}

public Producto getProductoVendido() {


return productoVendido;
}

public void setProductoVendido(Producto productoVendido) {


this.productoVendido = productoVendido;
}

@Override
public float calcularImporte(){
return productoVendido.calcularImporte()* unidadesVendidas;
//return getProductoVendido().calcularImporte()* unidadesVendidas;
}

El método calcularImporte actúa de forma polimórfica “en dependiendo” del tipo de producto
comprado, los productos de alimentación y ropa heredan el método de la clase Producto y los de
electrónica tienen su propio método (método sobre-escrito).

107 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Producto, Alimentacion, Ropa y Electronica

Las superclase abstrata Producto y las subclases Alimentación, Ropa y Electronica no se ven afectadas.

Los métodos calcularImporte de Producto y Electronica siguen igual:

// en la clase Producto
public float calcularImporte(){
return precio;
}

// en la clase Electronica
public float calcularImporte(){
return super.calcularImporte()+plusGarantia;
}

Venta

El arrayList listaCompra que era una colección de objetos de tipo Producto, ahora es una colección de
objetos de tipo LineaVenta

private ArrayList<LineaDeVenta> listaCompra = new ArrayList<LineaDeVenta>();

El parametro del método incluirEnListaCompra es del tipo LineaDeVenta


public void incluirEnListaCompra(LineaDeVenta linea){
listaCompra.add(linea);
}

En el método calcularImporte sólo se ha modificado el nombre del parámetro de la instrucción for


mejorada. Se puede utilizar una variable (parámetro) de tipo Valorable o de tipo LineaDeVenta para el
tratamiento polimórfico.

public float calcularImporte(){


float suma=0;
for(LineaDeVenta lineaActual:listaCompra){
suma+=lineaActual.calcularImporte();
}
return suma;
}

public float calcularImporte(){


float suma=0;

108 - 130
Tema 12. Programación orientada a objetos: polimorfismo

for(Valorable lineaActual:listaCompra){
suma+=lineaActual.calcularImporte();
}
return suma;
}

Sustituimos el método toString de la clase Venta por el método ticketDeCompra

Nombre de Producto Precio Unidades Importe


Lata de Tomate 1,80 5 9,00
Sudadera 14,50 2 29,00
Mp3 15,00 1 15,00
Importe 53,00

// método ticketDeCompra
public String ticketDeCompra(){
String ticket="";
for(LineaDeVenta lineaActual:listaCompra){
ticket+=lineaActual.getProductoVendido().getNombre();
ticket+='\t';
ticket+=lineaActual.getProductoVendido().calcularImporte();
ticket+='\t';
ticket+=lineaActual.getUnidadesVendidas();
ticket+='\t';
ticket+=lineaActual.calcularImporte();
ticket+='\n';
}
ticket+=" Importe total "+this.calcularImporte();
return ticket;
}

La variable lineaActual es del tipo LineaDeVenta

El método getProductoVendido de la clase LineDeVenta nos devuelve el atributo productoVendido de


tipo Producto.
public Producto getProductoVendido() {
return productoVendido;
}

El método getNombre de la clase Producto nos devuelve el nombre del producto.

De igual forma, la instrucción


lineaActual.getProductoVendido().calcularImporte()
nos devuelve el importe del producto (alimentación, ropa o electrónica) dependiendo del objeto
producto.

El método ticketDeCompra llama al método calcularImporte de la misma clase (clase Venta),


this.calcularImporte() o calcularImporte() es lo mismo, llama al método de la clase.

109 - 130
Tema 12. Programación orientada a objetos: polimorfismo

CarritoDeLaCompra

Se añade el siguiente código para recoger el nuevo requerimiento:

LineaDeVenta linea=null; // definimos una variable del tipo LineaDeVenta

. . .

System.out.print("Introducir unidades a comprar: ");


unidades=teclado.nextInt();

. . .

Se guarda la información del producto en el arrayList:


linea=new LineaDeVenta(new Alimentacion(codigoBarras, nombre, precio,
caducidad), unidades);

venta.incluirEnListaCompra(linea);

Se puede simplificar (no necesitamos declarar una variable de tipo


LineaDeVenta):

venta.incluirEnListaCompra(new LineaDeVenta(new
Alimentacion(codigoBarras,nombre,precio,caducidad),unidades));

Igual para los productos del tipo de la clase Ropa y de la clase Electronica.

110 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Versión 4.0 (Ampliación).

Cambiar el ticket de la compra para que aparezca en el nombre del producto (cuando sea ropa) el
material de que está hecha la prenda.

Nombre de Producto Precio Unidades Importe


Lata de Tomate 1,80 5 9,00
Sudadera Algodón 14,50 2 29,00
Mp3 15,00 1 15,00
Importe 53,00

111 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Solución propuesta

Tenemos que cambiar el método ticketDeCompra

// método ticketDeCompra
public String ticketDeCompra(){
String ticket="";
for(LineaDeVenta lineaActual:listaCompra){
ticket+=lineaActual.getProductoVendido().getNombre();
ticket+='\t';
ticket+=lineaActual.getProductoVendido().calcularImporte();
ticket+='\t';
ticket+=lineaActual.getUnidadesVendidas();
ticket+='\t';
ticket+=lineaActual.calcularImporte();
ticket+='\n';
}
ticket+=" Importe total "+this.calcularImporte();
return ticket;
}

Cuando procesamos objetos en forma polimórfica no se permite ejecutar métodos o


hacer referencia a atributos que pertenezcan sólo a la subclase desde una referencia a la
superclase o la interface.

No se puede hacer referencia desde la variable lineaActual al atributo material de la clase


Ropa.

public String ticketDeCompra(){


String ticket="";
for(LineaDeVenta lineaActual:listaCompra){
ticket+=lineaActual.getProductoVendido().getNombre();
ticket+='\t';
if(lineaActual.getProductoVendido() instanceof Ropa) {
Ropa ropa = (Ropa) lineaActual.getProductoVendido();
ticket+=" "+ropa.getMaterial();
}
ticket+='\t';
ticket+=lineaActual.getProductoVendido().calcularImporte();
ticket+='\t';
ticket+=lineaActual.getUnidadesVendidas();
ticket+='\t';
ticket+=lineaActual.calcularImporte();
ticket+='\n';
}
ticket+=" Importe total "+this.calcularImporte();
return ticket;
}

112 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Observaciones.

if (lineaActual.getProductoVendido() instanceof Ropa) {


Ropa ropa = (Ropa) lineaActual.getProductoVendido();
ticket+=" "+ropa.getMaterial();
}

lineaActual es un objeto de tipo LineaDeVenta

lineaActual.getProductoVendido() es un objeto tipo Producto

Otra forma:

if (lineaActual.getProductoVendido() instanceof Ropa) {


ticket+= " "+((Ropa) lineaActual.getProductoVendido()).getMaterial();
}

((Ropa) lineaActual.getProductoVendido()) es un objeto tipo Ropa.

113 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Versión 5.0 (Ampliación)

Mejorar el CarritoDeLaCompra permitiendo:

1- Eliminar una linea de venta (LineaDeVenta) a través del nombre del producto.

2- Modificar el número de unidades de una linea de Venta.

Para las dos acciones (borrar o modificar una linea de venta) se deberá pedir al usuario el nombre del
producto que se desea borrar o modificar.

Se escribirá el ticket de la compra y posteriormente se le pedirá al usuario si desea borrar algún


producto y si desea modificar las unidades de algún producto de la compra.

114 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Propuesta de solución

1.- Eliminar una linea de venta.

Se puede eliminar un elemento de un ArrayList de tres formas distintas:

a.- Eliminar la primera ocurrencia del objeto X.


remove(X)

Si el ArrayList no contiene el objeto especificado, la ArrayList permanece


sin cambios.

Este método realiza una búsqueda lineal y determina la igualdad


llamando a Object.Equals.

b.- Por su posición


remove(posición)

c.- Utilizando el método remove del iterador


nombreIterator = listaCompra.iterator();

nombreIterator.remove();

Después de borrar un elemento del ArrayList, los elementos se desplazan para


ocupar el espacio libre, se actualizan los índices de los elementos.

a.- Eliminar la ocurrencia del objeto.

En la calse Venta, definimos el método para borrar un elemento del arrayList:

public boolean eliminarEnListaCompra(LineaDeVenta linea){


return listaCompra.remove(linea);
}

En la clase CarritoDeLaCompra, preguntas si se desea borrar un producto (una


linea de venta) y se pide el nombre:

115 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Venta venta = new Venta();


…....
//Después de escribir el carrito de la compra preguntamos si quiere retirar algún producto
System.out.println("Su tickect de compra: \n ");
System.out.println(venta.ticketDeCompra());
System.out.println("Desea borrar algun procuto (s/n) ");
respuesta =teclado.next().charAt(0);
if (respuesta=='s'|| respuesta=='S') {
System.out.println("Introduzca el nombre del producto que deseas borrar");
nombre=teclado.next();
venta.eliminarEnListaCompra(venta.buscar(nombre));
}

En la clase Venta, tengo que definir el método buscar un elemento


(lineaDeVenta) del arrayList desde el nombre:

public LineaDeVenta buscar(String nombre){


for(LineaDeVenta lineaActual:listaCompra) {
if (lineaActual.getProductoVendido().getNombre().equals(nombre))
return lineaActual;
}
return null;
}

b.- Opción de borrado utilizando la posición del objeto en la colección,


implementa los siguientes pasos:

1. Cambiar el método ticket para que aparezca la posición del objeto,


numerar a partir de uno.

2. Pedir al usuario la posición de la linea de venta que se desea borrar.

3. Realizar el borrado a través del método remove(posición).

c.- Opción de borrado utilizando el método remove del iterador.

Venta venta = new Venta();


…....
//Después de escribir el carrito de la compra preguntamos si quiere retirar algún producto
System.out.println("Su tickect de compra: \n ");
System.out.println(venta.ticketDeCompra());
System.out.println("Desea borrar algun procuto (s/n) ");
respuesta =teclado.next().charAt(0);

116 - 130
Tema 12. Programación orientada a objetos: polimorfismo

if (respuesta=='s'|| respuesta=='S') {
System.out.println("Introduzca el nombre del producto que deseas borrar");
nombre=teclado.next();
//definir un objeto de tipo iterator
//recorrer la colección y buscar el objeto (lineaVenta) del producto
// al encontrar el objeto linea de venta con el producto de nombre igual al introducido,
// borra objeto
}

117 - 130
Tema 12. Programación orientada a objetos: polimorfismo

2.- Modificar las unidades del producto del carrito

Venta venta = new Venta();


…....
//Después de escribir el carrito de la compra preguntamos si quiere modificar las uds.

Buscamos el producto en el array de lineas de venta y modificamos el atributo unidades de dicho


objeto.

System.out.println("Desea cambiar las unidades de un procuto (s/n)");


respuesta =teclado.next().charAt(0);
if (respuesta=='s'|| respuesta=='S') {
System.out.println("Introduzca el nombre del producto del que desea cambiar las
uds.");
nombre=teclado.next();
LineaDeVenta lineaProducto =venta.buscar(nombre);
System.out.println("Unidades (valor actual): "+lineaProducto.getUnidadesVendidas());
System.out.println("Introduzca nuevo valor: ");
unidades=teclado.nextInt();
lineaProducto.setUnidadesVendidas(unidades);
}

118 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejercicio II. Proyecto Gestión de Fincas e Inmuebles.

Una empresa gestiona el alquiler de un conjunto de inmuebles que son de su propiedad. Cada inmueble
puede ser un local comercial, oficina o un piso.

Los datos de un inmueble son dirección, dimensión y alquilado (nos informa si el inmueble se
encuentra alquilado). Para el caso de pisos si está o no amueblado. Para el caso de los locales
comerciales si dispone de salida de humos y de licencia para negocios de restauración.

Cualquier persona que tenga una nómina o un aval bancario puede alquilar alguno de los pisos o locales
que no estén ya alquilados, y posteriormente desalquilarlo. Los datos requeridos para el contrato son
fecha de contrato, duración en meses, datos del inquilino (nombre y dni) y del inmueble. Además en el
contrato se fija la fianza y la renta mensual.

Se debe gestionar los movimientos bancarios que se producen asociados a cada oficina, piso o local.
Todos los movimientos bancarios hacen referencia a un determinado inmueble, tienen un concepto y
una cantidad (importe).

De cada movimiento se registra la fecha en que se ha realizado.

Un movimiento bancario puede ser de dos tipos: un gasto o un ingreso.

Un gasto está asociado a un inmueble determinado y se indicará el tipo de gasto al que pertenece entre
los que se tienen estipulados.

Existe dos tipos de gastos: los imputables al inquilino (luz, gas, etc..) y los no imputables a los inquilinos
(que serían las reparaciones y gastos de mantenimiento).

tipoMovimiento: TipoMovimiento

public enum TipoMovimiento {


INGRESO, GASTO_IMPUTABLE, GASTO_NO_IMPUTABLE
}

Cada mes se generan los recibos para cada uno de los inmuebles contratados.

El recibo especifica el inmueble (piso, local u oficina) a que pertenece, la fecha de emisión, la renta, el
agua, la luz, portería, …. la renta se cargará con un IVA del 21%.

GestionarInmuebles: es una aplicación que emite los recibos de los inmuebles contratados en un
determinado mes, coge la información de los movimientos de una cuenta bancaria ( numero de cuenta
y una colección de movimientos).

El programa creará, al menos, 3 contratos.

Ejemplo de ejecución:

119 - 130
Tema 12. Programación orientada a objetos: polimorfismo

En la cultura popular, divide y vencerás hace referencia a un refrán que implica resolver un problema difícil, dividiéndolo
en partes más simples tantas veces como sea necesario, hasta que la resolución de las partes se torna obvia. La solución del
problema principal se construye con las soluciones encontradas.
En las ciencias de la computación, el término divide y vencerás hace referencia a uno de los más importantes paradigmas
de diseño algorítmico. El método está basado en la resolución recursiva de un problema dividiéndolo en dos o más
subproblemas de igual tipo o similar. El proceso continúa hasta que éstos llegan a ser lo suficientemente sencillos como
para que se resuelvan directamente. Al final, las soluciones a cada uno de los subproblemas se combinan para dar una
solución al problema original.
Fuente: http://es.wikipedia.org/wiki/Algoritmo_divide_y_vencer%C3%A1s

Por su complejidad vamos a implementar el proyecto en diferentes fases.

Fase I

Una empresa gestiona el alquiler de un conjunto de inmuebles que son de su propiedad. Cada inmueble
puede ser un local comercial, oficina o un piso.

Los datos de un inmueble son dirección, dimensión y alquilado (nos informa si el inmueble se
encuentra alquilado). Para el caso de pisos si está o no amueblado. Para el caso de los locales
comerciales si dispone de salida de humos y de licencia para negocios de restauración.

Prueba los requerimientos anteriores desde una nueva clase GestionarInmuebles (que contiene el
método main()), crea los 3 tipos de inmueble y visualiza su contenido, como indica la figura.

Inmueble [dirección=Velazquez 31 2ºA Mérida, dimensión=120, alquilado=No]


Piso [amueblado=Sí]

Inmueble [dirección=Prim 1 Madrid, dimensión=480, alquilado=No]


Local comercial [salida humos=No, licencia restauración=No]

Inmueble [dirección=Goya 23 1ºA Badajoz, dimensión=230, alquilado=No]


Oficina []

120 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Diagrama UML

121 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Fase II

Una empresa gestiona el alquiler de un conjunto de inmuebles que son de su propiedad. Cada inmueble
puede ser un local comercial, oficina o un piso.

Los datos de un inmueble son dirección, dimensión y alquilado (nos informa si el inmueble se
encuentra alquilado). Para el caso de pisos si está o no amueblado. Para el caso de los locales
comerciales si dispone de salida de humos y de licencia para negocios de restauración.

Cualquier persona que tenga una nómina o un aval bancario puede alquilar alguno de los pisos o locales
que no estén ya alquilados, y posteriormente desalquilarlo. Los datos requeridos para el contrato son
fecha de contrato, duración en meses, datos del inquilino (nombre y dni) y del inmueble. Además en el
contrato se fija la fianza y la renta mensual.

Prueba los requerimientos anteriores desde una nueva clase GestionarInmuebles, crea 3 o más
contratos y visualiza su ejecución como indica la figura.

ERROR NO SE PUEDE ALQUILAR:


Inmueble [dirección=Velazquez 31 2ºA Mérida, dimensión=120, alquilado=Sí]
Piso [amueblado=Sí]
ERROR NO SE PUEDE ALQUILAR:
Inmueble [dirección=Goya 23 1ºA Badajoz, dimensión=230, alquilado=Sí]
Oficina []

Se han realizado 1 contratos:

Contrato [fecha=1-2-2013, meses=120, Inmueble [dirección=Prim 1 Madrid,


dimensión=480, alquilado=No]
Local comercial [salida humos=No, licencia restauración=No]
Inquilino [dni=47748383-Y, nombre=Embutidos Extremeños, aval=AVAL_BANCARIO],
fianza=3000.0, renta=3150.0]

Uso de enumerados

public enum TipoAval {


AVAL_BANCARIO,NOMINA
}

122 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Diagrama UML

123 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Fase III

Se debe gestionar los movimientos bancarios que se producen asociados a cada oficina, piso o local.
Todos los movimientos bancarios hacen referencia a un determinado inmueble, tienen un concepto y
una cantidad (importe).

De cada movimiento se registra la fecha en que se ha realizado.

Un movimiento bancario puede ser de dos tipos: un gasto o un ingreso.

Un gasto está asociado a un inmueble determinado y se indicará el tipo de gasto al que pertenece entre
los que se tienen estipulados.

Existe dos tipos de gastos: los imputables al inquilino (luz, gas, etc..) y los no imputables a los inquilinos
(que serían las reparaciones y gastos de mantenimiento).

tipoMovimiento: TipoMovimiento

public enum TipoMovimiento {


INGRESO, GASTO_IMPUTABLE, GASTO_NO_IMPUTABLE
}

124 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Prueba la funcionalidad del modelo creando varios movimientos en la clase GestionarInmuebles.

Listado de movimientos:
1. 15-2-2013 GASTO_IMPUTABLE Recibo Gas 65.0€. Inmueble:Velazquez 31 2ºA Mérida
2. 15-3-2013 GASTO_NO_IMPUTABLE Comunidad de vecinos 150.0€. Inmueble:Velazquez 31 2ºA Mérida
3. 15-4-2013 GASTO_IMPUTABLE Recibo Luz 70.0€. Inmueble:Velazquez 31 2ºA Mérida

125 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Fase IV

Cada mes se generan los recibos para cada uno de los inmuebles contratados.

El recibo especifica el inmueble (piso, local u oficina) a que pertenece, la fecha de emisión, la renta, el
agua, la luz, portería, la actualización del IPC anual, …. la renta se cargará con un IVA del 21%.

GestionarInmuebles: emisión de recibos según mes.

Ejemplo de recibo

RECIBO DEL INMUEBLE Velázquez 31 2ºA Mérida DEL MES 4


ARRENDADOR José Luis Sancho Duran
Recibo Gas 65.0
Recibo Luz 70.0
TOTAL GASTOS: 135.0
RENTA(21% IVA): 1028.5
TOTAL: 1163.5

RECIBO DEL INMUEBLE Prim 1 Madrid DEL MES 4


ARRENDADOR Embutidos Extremeños
Recibo Luz 70.0
TOTAL GASTOS: 70.0
RENTA(21% IVA): 3811.5
TOTAL: 3881.5

RECIBO DEL INMUEBLE Goya 23 1ºA Badajoz DEL MES 4


ARRENDADOR Software Consultores S.A.
TOTAL GASTOS: 0.0
RENTA(21% IVA): 2480.5
TOTAL: 2480.5

dos formas de pasar los movimientos del inmueble al recibo:

Primera:

126 - 130
Tema 12. Programación orientada a objetos: polimorfismo

En la clase contrato:

public String emitirRecibo(int mes, CuentaBancaria cuenta){


String recibo="";
ArrayList<Movimiento> movimientosInmueble=null;
movimientosInmueble= cuenta.extraerMovimientosMesInmueble(this.inmueble, mes);
...

En la clase cuenta bancaria:

movimientosCuenta es un atributo de tipo ArrayList de la clase CuentaBancaria.

public ArrayList<Movimiento> extraerMovimientosMesInmueble(Inmueble inmueble,


int mes){
ArrayList <Movimiento> movimientosInmuebleMes=new ArrayList<Movimiento>();
for (Movimiento movimientoActual: movimientosCuenta){
if (movimientoActual.getInmueble().equals(inmueble) &&
movimientoActual.getFecha().get(Calendar.MONTH)==mes)

movimientosInmuebleMes.add(movimientoActual);

}
return movimientosInmuebleMes;
}

Segunda:

public String emitirRecibo(int mes, CuentaBancaria cuenta){


String recibo="";
ArrayList<Movimiento> movimientosInmueble = new ArrayList<Movimiento>();
cuenta.extraerMovimientosMesInmueble(this.inmueble, mes, movimientosInmueble);

}

public void extraerMovimientosMesInmueble(Inmueble inmueble, int mes,


ArrayList<Movimiento> movimientosInmueble ){
for (Movimiento movimientoActual: movimientosCuenta){
if (movimientoActual.getInmueble().equals(inmueble) &&
movimientoActual.getFecha().get(Calendar.MONTH)==mes)
movimientosInmueble.add(movimientoActual);
}

Recuerda: pág.68

127 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Fase V

En la fase anterior se obviaron algunos requerimientos que se implementan en esta fase.

1º Se desea saber si el recibo que se emite se encuentra o no pagado, dicha información es externa a las
clases implementadas y no se encuentra guardada en ningún sitio. Lógicamente cualquier aplicación de
gestión inmobiliaria deberá guardar esta información.

Optamos por incluirla en el recibo la información preguntando al usuario si el recibo correspondiente a


un determinado contrato en un determinado mes se encuentra pagado (sí o no).

2º Los pisos pagan todos los conceptos anteriores. Las oficinas pagan además limpieza. Los locales
comerciales no pagan portería y si pagan el impuesto mensual de vado.

Los movimientos recogen los ingresos y gastos de los inmuebles, pero en ningún caso se ha tenido en
cuenta que determinado concepto (gasto) sólo es aplicable a un determinado inmueble.

Del requerimiento se deduce:

– en los movimientos (gastos) de pisos y oficinas no se debe comprobar nada.


– en los movimientos (gastos) de locales hay que comprobar que su concepto no es portería.

128 - 130
Tema 12. Programación orientada a objetos: polimorfismo

Ejercicio II. Gestión de fincas e inmuebles

Una empresa gestiona el alquiler de un conjunto de inmuebles que son de su propiedad. Cada inmueble
puede ser un local comercial, oficina o un piso.

Los datos de un inmueble son dirección, dimensión y alquilado (nos informa si el inmueble se
encuentra alquilado). Para el caso de pisos si está o no amueblado. Para el caso de los locales
comerciales si dispone de salida de humos y de licencia para negocios de restauración.

Cualquier persona que tenga una nómina o un aval bancario puede alquilar alguno de los pisos o locales
que no estén ya alquilados, y posteriormente desalquilarlo. Los datos requeridos para el contrato son
fecha de contrato, duración en meses, datos del inquilino (nombre y dni) y del inmueble. Además en el
contrato se fija la fianza y la renta mensual.

Cada mes se generan los recibos para cada uno de los inmuebles, el cual lleva asociado un número de
recibo que es único para cada contrato.

El recibo especifica el piso o local a que pertenece, la fecha de emisión, la renta, el agua, la luz, portería,
la actualización del IPC anual e importe de IVA(21%). Además, para cada recibo se desea saber si está o
no cobrado.

Los pisos pagan todos los conceptos anteriores. Las oficinas pagan además limpieza. Los locales
comerciales no pagan portería y si pagan el impuesto mensual de vado.

Se debe gestionar los movimientos bancarios que se producen asociados a cada edificio, piso o local.
Todos los movimientos bancarios hacen referencia a un determinado inmueble, tienen un concepto y
una cantidad (importe).

De cada movimiento se registra la fecha en que se ha realizado.

Un movimiento bancario puede ser de dos tipos: un gasto o un ingreso.

Un gasto está asociado a un inmueble determinado y se indicará el tipo de gasto al que pertenece entre
los que se tienen estipulados.

Existe dos tipos de gastos: los imputables al inquilino (luz, gas, etc..) y los no imputables a los inquilinos
(que serían las reparaciones y gastos de mantenimiento).

Un ingreso está asociado a un inmueble determinado y se indicará el tipo de ingreso al que pertenece,
como en el caso de los gastos. (Ejemplos de ingresos son precisamente los recibos que se cobran cada
mes a los inquilinos o la fianza aportada al firmar el contrato).

Basándose en los gastos e ingresos que se deducen de los movimientos bancarios, la aplicación deberá
ser capaz de ocuparse de la gestión económica generando los informes que indican el beneficio que se
obtiene de cada inmueble.

GestionarInmuebles (que contiene el método main()) realizará las siguientes acciones: Cartera de
inmuebles (creación de tres inmuebles), cartera de inquilinos (creación de tres inquilinos) , contratos
(creación de tres contratos ), registro de movimientos bancarios (creación de varios movimientos
bancarios) y emisión de recibos según mes.

129 - 130
Tema 12. Programación orientada a objetos: polimorfismo

130 - 130

Você também pode gostar