Polimorfismo
Introducción
Como sabemos, Java es de tipado estático y estricto. Salvo contadas excepciones (promociones automáticas y conversiones), a una variable de tipo primitivo, no se le podrá asignar un valor de un tipo diferente al suyo. Del mismo modo, una variable de tipo referencia de una clase, no podrá eferenciar un objeto de otra clase. Existe, sin embargo, una importante excepción de este último supuesto: “A una variable de tipo referencia de una superclase, se le podrá asignar una referencia a un objeto de cualquiera de sus subclases”
Ejemplo de Polimorfismo con Herencia
Es necesario una relación jerárquica entre las clases para poder usar el polimorfismo. Para el polimorfismo se utiliza ligadura dinámica.
Ligadura dinámica: Cuando se llama a un método sobrescrito mediante una referencia a una superclase, java determina qué versión del método ejecutar basándose en el tipo del objeto al que se referencia. El funcionamiento descrito en el enunciado anterior recibe el nombre de “despacho dinámico de métodos”, “ligadura dinámica”, …. Y otros nombres. Esta técnica consiste en que java puede decidir que versión de método sobreescrito a ejecutar en tiempo de ejecución, de ahí lo de “dinámico”. Poder tomar esta decisión en tiempo de ejecución es muy importante porque es uno de los mecanismos con los que java soporta el polimorfismo.
La ligadura dinámica se consigue con dos mecanismos analizados anteriormente:
- Asignar a una referencia de superclase, un objeto de subclase:
class A{
int a;
A(int i){a=i;}
}
class B extends A{
int b;
B(int i, int j){
super(i);
b=j;
}
}
class C{
int c;
C(int k){c=k;}
}
class Unidad4{
public static void main(String[] args) {
A a1 = new A(2);
A a2;
B b = new B(2,4);
C c=new C(8);
a2=a1; //ningun problema, a2 y a1 son del mismo tipo
//a2=c; // mal, a2 y c no son tipos compatibles
a2=b; //bien, una referencia a superclase puede referenciar a un objeto subclase
System.out.println(a2.a);//bien
// System.out.println(a2.b); //mal, a2 sólo puede acceder a la parte de superclase
}
}
- Sobreescribir un método de la superclase en todas sus subclases:
Primero creamos em método genérico en la superclase (comportamiento común):
public class Animal {
...
public String habla() { return "Animal no habla!!"; }
}
Sobreescribimos en cada subclase el método habla (especificación funcional):
public class Gato extends Animal {
public Gato() {
System.out.println("> Constructor de Gato");
}
public String habla() { return "Miau!!";}
}
public class Perro extends Animal {
public Perro() {
System.out.println("> Constructor de Perro");
}
public String habla() { return "Guau!!";}
}
public class Fauna {
public static void main(String[] args) {
...
Animal[] lista = new Animal[3]; //referencias a la superclase
lista[0] = new Gato("Tom", 7);
lista[1] = new Perro("Scooby", 10);
lista[2] = new Estudiante("Rigby", 14); //Añadimos al array varias instancias de las distintassubclases
for(Animal a: lista)
System.out.println(a.habla());
}
}
Observamos como, en cada caso, se ha empleado el tipo del objeto referenciado y no el tipo de la variable, para determinar qué implementación del método se ejecuta.
Conversión (cast) de referencias
Podemos usar el operador cast para que una referencia de un tipo A se convierta en una Referencia de un tipo B. La conversión la hace el programador por su cuenta y riesgo, si hay algún tipo de incompatibilidad “salta” una excepción en tiempo de ejecución. ¿Para qué hacer cast de referencias?. Suponemos que B extiende a A como el ejemplo de abajo. Si yo como programador tengo una referencia a de tipo A que referencia a un objeto de tipo B(ver apartado anterior), me puede interesar convertir esa referencia de tipo A en tipo B para tener acceso a todas las partes del objeto B.
Ejemplo:
class A {
public int atrib1;
}
class B extends A {
public int atrib2;
}
public class Unidad4{
public static void main(String[] args) {
int temp;
A a; // Referencia a objetos de la clase A
a= new B (); // Referencia a objetos clase A, pero apunta realmente a objeto clase B
//temp=a.atrib2; //error, obj apunta a un objeto de clase B pero solo tiene acceso a parte de superclase
B b= (B) a;//la refencia a la convierto a otra de tipo B
temp=b.atrib2;
}
}
El operador instance of
En general “preferimos” trabajar con referencias de superclase para lucrarnos del mecanismo de polimorfismo, no obstante, puede darse el caso que necesite por alguna razón saber realmente a qué tipo de subclase está apuntando la referencia de la superclase o como comprobación previa para hacer un cast (punto anterior). Para ello utilizamos el operador instance of, por ejemplo si f es una referencia de tipo Figura
if(f instance of Triangulo)
System.out.println("esta figura es un triángulo");
Ejemplo de Polimorfismo con Interfaces
- Java nos permite crear variables referencia cuyo tipo sea un interface.
- Una variable de este tipo podrá referenciar a cualquier objeto que implemente dicho interface.
- Al invocar un método de un objeto a través de una referencia de tipo interface, se ejecutará la versión implementada por el objeto referenciado.
- En tiempo de ejecución ligadura.
- El proceso es similar al empleo de variables del tipo de la superclase para “mapear” el acceso a las implementaciones sobreescritas de sus métodos a través de los objetos de sus subclases.
Ejemplo:
public interface MiInterface {
public void calcularArea();
public void calcularPerimetro();
}
public class Triangulo implements MiInterface{
int base = 3;
int altura = 5;
@Override
public void calcularArea() {
System.out.println(base*altura);
}
@Override
public void calcularPerimetro() {
}
}
public class Cuadrado implements MiInterface{
int lado = 4;
@Override
public void calcularArea() {
System.out.println(lado*lado);
}
@Override
public void calcularPerimetro() {
}
}
public class Principal {
public static void main(String[] args) {
MiInterface tri = new Triangulo();
MiInterface cua = new Cuadrado();
tri.calcularArea();
cua.calcularArea();
}
}
Ejercicios
Ejercicio 1:
Volvemos al ejercicio de Series. Usamos las mismas clases y creamos en el siguiente main():
class Unidad4{
public static void main(String[] args) {
MasDos serie1 = new MasDos();
MasDos serie2= new MasDos();
MasTres serie3 = new MasTres();
MasTres serie4= new MasTres();
serie2.establecerInicio(200);
serie4.establecerInicio(300);
System.out.print("\nSerie1: ");
for(int i=0;i<5;i++)
System.out.print(serie1.obtenerSiguiente()+" ");
System.out.print("\nSerie2: ");
for(int i=0;i<5;i++)
System.out.print(serie2.obtenerSiguiente()+" ");
System.out.print("\nSerie3: ");
for(int i=0;i<5;i++)
System.out.print(serie3.obtenerSiguiente()+" ");
System.out.print("\nSerie4: ");
for(int i=0;i<5;i++)
System.out.print(serie4.obtenerSiguiente()+" ");
}
}
SE PIDE: Generando exactamente la misma salida que con el código anterior, escribir un código mejorado que utilice un array de interfaces de tipo Serie para evitar tanto código duplicado.
Ejercicio 2:
Escribe código para implantar la siguiente estructura. Luego en main de Unidad4 crea 3 triángulos, 3 rectángulos y 3 círculos almacenando las figuras en un array. Recorre el array e imprime el area y color de todas las figuras, cambiando el color a “negro” en aquellas figuras cuya área se mayor que 4.0.:
Ejercicio 3:
Se necesita crear un sistema para El control de pago del personal para la compañía Marejada Feliz. El mismo debe contar con:
- Clase Barco:
- Con los atributos: Nombre y tipo de tipo String, Capacidad de pasajero y capacidad de carga tipo int.
- Método para mostrar todos los datos del barco
- Clase GPS:
- Con los atributos: coordenadas en X, coordenada Y, fecha y hora de tipo String, días tripulado tipo int.
- Clase abstracta Tripulante:
- El mismo debe tener los siguientes atributos: numero carnet, posición gps, edad, tiempo en la empresa de tipo int; nombre y telefono tipo String, sexo tipo char, barco de tipo barco
- Métodos abstractos sueldo y mostrar datos
- El método sueldo se calculara según el rango de cada tripulante en el barco
- El método mostrar dato deberá mostrar todos los datos (atributos) según la clase derivada.
- Clase Capitan:
- Atributos horas de experiencia tipo int, constante sueldo de 4.500.000, sueldo total y bono tipo float.
- Método propio para calcular el bono de la siguiente manera:
- Si las horas de experiencia es mayor igual a 5000 y menor a 150000 tendrá un bono del 20%
- Si las horas de experiencia es mayor igual a 150000 y menor a 300000 tendrá un bono del 40%. Y si es mayor a 300000 será un 75% de bono.
- Su sueldo total se calculará: sueldo mas bono.
- Clase Jefe de flota:
- Atributos peso Pescado y peso mariscos tipo int, constante sueldo de 350.000.000, sueldo total y bono pescado y bono mariscos tipo float.
- Método propio para calcular los bonos de la siguiente manera:
- Si son pescados, se multiplicará la cantidad 1 y si son mariscos por 2.
- Su sueldo total se calculará: sueldo mas bonos.
- Clase Marinero:
- Atributos peso total pescado tipo int, constante sueldo de 90.000, sueldo total y bono tipo float.
- Método propio para calcular el bono de la siguiente manera:
- Si la cantidad pescada es mayor o igual a 1 se multiplicara por 0.25
- Su sueldo total se calculará: sueldo mas bonos.
Al menos prueba el programa para 7 marineros/jefe de flota/capitan y comprueba el polimorfismo en tu arquitectura O.O.