Polimorfismo en Java -Interface- (Parte II), con ejemplos
El proyecto de este post lo puedes descargar pulsando AQUI.
En esta entrada vamos a continuar hablando de polimorfismo y en concreto vamos a explicar las "Interface" y como siempre lo vamos ha hacer con un ejemplo. Para entender lo que vamos a contar en esta entrada es imprescindible que sepais que es la herencia y el polimorfismo, por tanto sino teneis muy claros estos conceptos recomendamos que mireis los siguientes tutoriales en los que hablamos sobre ello:
- Herencia -> Herencia en Java, con ejemplos
- Polimorfismo -> Polimorfismo en Java (Parte I), con ejemplos
El concepto de Interface lleva un paso más alla el concepto de una clase abstracta en la que vimos que una clase abstracta es una clase que no se puede instanciar (crear un objeto de esa clase) pero si se pueden definir atributos e implementar métodos en ella para que sus clases hijas los puedan utilizar. Pues bien una Interface es una clase abstracta pura en la que todos sus métodos son abstractos y por tanto no se pueden implementar en la clase Interface. Mucho podreis pensar para que vale una clase abstracta pura en la que no se permiten implementar métodos y que encima en las clases hijas de esa interface tengan que tener "si o si" implementados estos métodos; pues bien, las Interfaces sirven para establecer la forma que debe de tener una clase. Un ejemplo que hay en Java sobre Interace es la Interface Map. En la entrada "Map en Java, con ejemplos", vimos que habia diferentes tipos de Map; en concreto los HashMap, TreeMap y LinkedHashMap, lo que quiere decir que todas las clases Map deben de tener implementadas a su manera los mismo métodos como el "put()", "get()", "remove()", etc. y asi lo vimos en esta entrada en la que un HashMap inserta los datos de manera diferente a un TreeMap y al LinkedHashMap. Por tanto vemos como se ha establecido una forma común que deben de respetar todos los Maps de Java.
Por otro lado se ha de decir que una Interface no se pueden definir atributos salvo que estos sean estaticos o constantes; es decir, "static" o "final".
Siguiendo con los ejemplos que hemos estado poniendo en las entradas de la Herencia y el Polimorfismo, vamos a ver un ejemplo en el que simularemos el comportamiento que tendrían los diferentes integrantes de la selección española de fútbol; tanto los Futbolistas como el cuerpo técnico (Entrenadores, Masajistas, etc…). Para este ejemplo nos vamos a basar en el siguiente diagrama de clases:
Lo primero que vemos es la clase "IntegranteSeleccionFutbol" que es una Interface en la que tiene definido cuatro métodos (es decir una clase abstracta pura). Como se ha dicho en esta clase solo se definen los métodos pero no se implementan; así que a nivel de código la clase quedaria de la siguiente manera:
public interface IntegranteSeleccionFutbol { void concentrarse(); void viajar(); void entrenar(); void jugarPartido(); }
Lo siguiente que vemos en el diagrama de clases es la clase "SeleccionFutbol" que es una clase abstracta que utiliza (implements) la Interface "IntegranteSeleccionFutbol". Al ser esta una clase abstracta no se puede instanciar y por tanto en ella no es necesario implementar los métodos de la "Interfece"; pero si será obligatorio implementarlo en sus clases hijas (Futbolista, Entrenador y Masajista). Para que veamos bien el uso de las interfaces, vamos ha hacer primero una "chapuzilla" para entender su utilización. Lo que vamos ha hacer es "no implementar los métodos de la interface en la clase abstracta SeleccionFutbol" con el fin de que veamos que en las clases hijas nos van a exigir que implementemos los métodos de la interface. Para ello la clase abstracta quedaría de la siguiente forma (sin ningún método implementado):
public abstract class SeleccionFutbol implements IntegranteSeleccionFutbol { protected int id; protected String nombre; protected String apellidos; protected int edad; public SeleccionFutbol() { } public SeleccionFutbol(int id, String nombre, String apellidos, int edad) { this.id = id; this.nombre = nombre; this.apellidos = apellidos; this.edad = edad; } // getter y setter }
En este punto vemos una nueva palabra reservada como es la palabra "implements" que quiere decir que la clase "SeleccionFutbol" debe ser una clase que adopte la forma que tiene la Interface "IntegranteSeleccionFutbol"; es decir, que debe tener implementados los métodos de la Interface. En este caso al ser una clase abstracta las podemos implementar ahi (si queremos) o en las clases hijas (obligatoriamente sino las implementamos en la clase padre), por eso el ejemplo que ponemos es para ver como el compilador nos exigirá que esos métodos estén implementados en las clases hijas.
Por tanto sino implementamos los métodos en la clase padre ("SeleccionFutbol") y nos vamos a cualquiera de las clases hijas (por ejemplo la de futbolista) vemos como nos exigen que implementemos esos métodos:
Vemos en la clase futbolista (en el IDE de Eclipse) que nos da un error y como solución nos dice que tenemos que implementar los métodos "concentrarse()", "viajar()", etc. Si en Eclipse seleccionamos una de las opciones para solucionar el error, vemos que nos da la opción de "Añadir los métodos no implementados" (add unimplemented methods):
Si seleccionamos esa opción vemos como en Eclipse nos implementa los métodos de la Interface de forma automática:
Como ya hemos visto que nos exigen implementar los métodos en la Interface en las clases hijas, ahora vamos ha hacer las cosas bien y vamos a implementar los métodos de la Interface en la clase padre ("SeleccionFutbol") para que solamente haya que implementar los métodos que queramos especializarlos en las clases hijas. Por tanto la clase "SeleccionFutbol" bien implementada quedaria de la siguiente forma:
public abstract class SeleccionFutbol implements IntegranteSeleccionFutbol { protected int id; protected String nombre; protected String apellidos; protected int edad; // Constructor, getter y setter public void concentrarse() { System.out.println("Concentrarse (Clase Padre)"); } public void viajar() { System.out.println("Viajar (Clase Padre)"); } public void entrenar() { System.out.println("Entrenar (Clase Padre)"); } public void jugarPartido() { System.out.println("Asiste al Partido de Fútbol (Clase Padre)"); } }
Y en las clases hijas solo implementaremos los métodos de la clase padre que queramos redefinir, quedando las clases hijas de la siguiente forma (recordar que la etiqueta "@Override" significa que ese método esta siendo redefinido)::
public class Futbolista extends SeleccionFutbol { private int dorsal; private String demarcacion; // Constructor, getter y setter @Override public void entrenar() { System.out.println("Realiza un entrenamiento (Clase Futbolista)"); } @Override public void jugarPartido() { System.out.println("Juega un Partido (Clase Futbolista)"); } public void entrevista() { System.out.println("Da una Entrevista"); } }
public class Entrenador extends SeleccionFutbol { private int idFederacion; // Constructor, getter y setter @Override public void entrenar() { System.out.println("Dirige un entrenamiento (Clase Entrenador)"); } @Override public void jugarPartido() { System.out.println("Dirige un Partido (Clase Entrenador)"); } public void planificarEntrenamiento() { System.out.println("Planificar un Entrenamiento"); } }
public class Masajista extends SeleccionFutbol { private String titulacion; private int aniosExperiencia; // Constructor, getter y setter @Override public void entrenar() { System.out.println("Da asistencia en el entrenamiento (Clase Masajista)"); } public void darMasaje() { System.out.println("Da un Masaje"); } }
Llegados a este punto ya tenemos implementada las misma funcionalidad que hicimos en la entrada de Polimorfismo en Java (Parte I), con ejemplos pero diseñada e implementada de otra forma, así que vamos a poner el código que escribimos para esa entrada y el resultado de la ejecución del mismo para que podais ver el resultado:
public class Main { // ArrayList de objetos SeleccionFutbol. Idenpendientemente de la clase hija a la que pertenezca el objeto public static ArrayList integrantes = new ArrayList(); public static void main(String[] args) { SeleccionFutbol delBosque = new Entrenador(1, "Vicente", "Del Bosque", 60, 28489); SeleccionFutbol iniesta = new Futbolista(2, "Andres", "Iniesta", 29, 6, "Interior Derecho"); SeleccionFutbol raulMartinez = new Masajista(3, "Raúl", "Martinez", 41, "Licenciado en Fisioterapia", 18); integrantes.add(delBosque); integrantes.add(iniesta); integrantes.add(raulMartinez); // CONCENTRACION System.out.println("Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)"); for (SeleccionFutbol integrante : integrantes) { System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> "); integrante.concentrarse(); } // VIAJE System.out.println("nTodos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método)"); for (SeleccionFutbol integrante : integrantes) { System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> "); integrante.viajar(); } // ENTRENAMIENTO System.out.println("nEntrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización)"); for (SeleccionFutbol integrante : integrantes) { System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> "); integrante.entrenar(); } // PARTIDO DE FUTBOL System.out.println("nPartido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización)"); for (SeleccionFutbol integrante : integrantes) { System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> "); integrante.jugarPartido(); } // PLANIFICAR ENTRENAMIENTO System.out.println("nPlanificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento:"); System.out.print(delBosque.getNombre() + " " + delBosque.getApellidos() + " -> "); ((Entrenador) delBosque).planificarEntrenamiento(); // ENTREVISTA System.out.println("nEntrevista: Solo el futbolista tiene el método para dar una entrevista:"); System.out.print(iniesta.getNombre() + " " + iniesta.getApellidos() + " -> "); ((Futbolista) iniesta).entrevista(); // MASAJE System.out.println("nMasaje: Solo el masajista tiene el método para dar un masaje:"); System.out.print(raulMartinez.getNombre() + " " + raulMartinez.getApellidos() + " -> "); ((Masajista) raulMartinez).darMasaje(); } }
Como resultado a la ejecución de este programa tenemos lo siguiente:
Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método) Vicente Del Bosque -> Concentrarse (Clase Padre) Andres Iniesta -> Concentrarse (Clase Padre) Raúl Martinez -> Concentrarse (Clase Padre) Todos los integrantes viajan para jugar un partido. (Todos ejecutan el mismo método) Vicente Del Bosque -> Viajar (Clase Padre) Andres Iniesta -> Viajar (Clase Padre) Raúl Martinez -> Viajar (Clase Padre) Entrenamiento: Todos los integrantes tienen su función en un entrenamiento (Especialización) Vicente Del Bosque -> Dirige un entrenamiento (Clase Entrenador) Andres Iniesta -> Realiza un entrenamiento (Clase Futbolista) Raúl Martinez -> Da asistencia en el entrenamiento (Clase Masajista) Partido de Fútbol: Todos los integrantes tienen su función en un partido (Especialización) Vicente Del Bosque -> Dirige un Partido (Clase Entrenador) Andres Iniesta -> Juega un Partido (Clase Futbolista) Raúl Martinez -> Asiste al Partido de Fútbol (Clase Padre) Planificar Entrenamiento: Solo el entrenador tiene el método para planificar un entrenamiento: Vicente Del Bosque -> Planificar un Entrenamiento Entrevista: Solo el futbolista tiene el método para dar una entrevista: Andres Iniesta -> Da una Entrevista Masaje: Solo el masajista tiene el método para dar un masaje: Raúl Martinez -> Da un Masaje
CONCLUSIONES Y ACLARACIONES:
Como hemos visto el concepto de la Interface va un paso más alla en lo referente al concepto de la clase abstracta. Es una muy buena práctica de diseño la utilización de clases Interface para definir clases que tengan una misma forma, aunque en ellas realicen comportamientos distintos.
Al igual que comentamos en las conclusiones de la entrada del polimorfismo, es muy probable que los que empeceis con la POO no le veais mucho sentido al tema de las clases Interface, así que no os preocupeis si eso es asi porque estos conceptos se consolidan a base de experiencia y de ir adquiriendo más conocimientos de arquitectura y diseño del software.
En la clase abstracta «SeleccionFutbol» se podría definir a su vez como «abstracto» el método «entrenar» ??
Por que sino creo que a las clases hijas no será obligatorio re-describir el método padre como si pasaba en el ejemplo de la
«parte 1», no ??
La clase «entrenamiento» en «SeleccionFutbol» y es abstracto en el ejemplo:
public abstract void entrenamiento();
Hola,
gracias por tu trabajo y por compartirlo.
He modificado el código del método main() para crear objetos de la clase correspondiente en lugar de la clase SeleccionFutbol:
public static void main(String[] args) {
//SeleccionFutbol delBosque = new Entrenador(1, «Vicente», «Del Bosque», 60, 28489);
//SeleccionFutbol iniesta = new Futbolista(2, «Andres», «Iniesta», 29, 6, «Interior Derecho»);
//SeleccionFutbol raulMartinez = new Masajista(3, «Raúl», «Martinez», 41, «Licenciado en Fisioterapia», 18);
Entrenador delBosque = new Entrenador(1, «Vicente», «Del Bosque», 60, 28489);
Futbolista iniesta = new Futbolista(2, «Andres», «Iniesta», 29, 6, «Interior Derecho»);
Masajista raulMartinez = new Masajista(3, «Raúl», «Martinez», 41, «Licenciado en Fisioterapia», 18);
integrantes.add(delBosque);
integrantes.add(iniesta);
integrantes.add(raulMartinez);
No hace falta hacer la conversión al llamar a los métodos propios de cada clase hija:
// PLANIFICAR ENTRENAMIENTO
//((Entrenador) delBosque).planificarEntrenamiento();
delBosque.planificarEntrenamiento();
// ENTREVISTA
//((Futbolista) iniesta).entrevista();
iniesta.entrevista();
// MASAJE
//((Masajista) raulMartinez).darMasaje();
raulMartinez.darMasaje();
Está explicado en el repositorio que he creado a partir del tuyo:
https://github.com/paco-portada/Polimorfismo
Un saludo, paco
Hola, he intentado probar el ejemplo en un proyecto en eclipse y me da un error en
for (SeleccionFutbol integrante : integrantes)
Type mismatch: cannot convert from element type Object to SeleccionFutbol
en cambio si hago
Iterator it = integrantes.iterator();
y pruebo con
while(it.hasNext())
{
it.next()…
}
me funciona
Puede que sea por la versión de Java ¿Qué versión de Java estás usando? pues iterar con iterator y hacerlo en un bucle for debería devolver el mismo resultado.
Me quedo bastante claro, despues de ver este tutorial
En el caso en el que la clase selección fut tuviera sus atributos privados y no protected cambia el código a utilizar para que la clase futbolista puedo implementar esos atributos en sus métodos?
Si te entendido bien, podrías poner getter y setter (que sean protected) para los atributos protected convertirlos en privados.
System.out.println("Todos los integrantes comienzan una concentracion. (Todos ejecutan el mismo método)");
for (SeleccionFutbol integrante : integrantes) {
System.out.print(integrante.getNombre() + " " + integrante.getApellidos() + " -> ");
integrante.concentrarse();
}
No entiendo la syntaxis del ciclo for. Como recorre los integrantes, porque no es un ciclo for tradicional ?
Este tipo de bucles «for» no se llaman «for» sino «foreach» (para no confundirlo con el ciclo «for» tradicional 😉 ). El bucle «for» asociado sería:
for(Iterator integrantesIter = integrantes.iterator(); integrantesIter.hasNext(); ) {
SeleccionFutbol integrante = integrantesIter .next();
integrante.concentrarse();
}
Como puedes ver, tiene muchos más código que el que has escrito 😀
Los bucles «foreach» sirven para facilitar recorrer listados (como arrays, List, Maps, etc). Te recomiendo que te familiarices porque son muy cómodos de utilizar (tanto que una vez los conozcas no querras utilizar el «for» tradicional).
El bucle «foreach» viene a decir (donde «ListadoDeElementos» puede ser un List, y cada objeto del tipo «ClaseDelElemento» uno de los muchos objetos que guardará el List anterior):
for (ClaseDelElemento elementoDelListado: ListadoDeElementos){
//Aquí podemos trabajar con cada «elementoDelListado»
}
Otro ejemplo de uso muy cómodo es para recorrer Arrays, como:
int[] miArray = new int[] {1, 2, 3, 4};
for (int valorDelArray: miArray) {
// Aquí utilizaremos el «valorDelArray», que en cada vuelta al bucle corresponderá con: 1, 2, 3 y 4
}
Excelente didáctica Ricardo y equipo. Salvo las observaciones que ya te comentaron algunas personas, está muy clara la exposición de los temas, y enfocando exactamente en el tema específico.
Te repito los tips a tener en cuenta:
1-Una relación de herencia responde a la pregunta "es un", (ya te lo mencionaron en las entradas de Herencia), por lo tanto, la clase SeleccionFutbol más bien tendría que llamarse "MiembroDeSeleccion", de forma que: un jugador "es un" MiembroDeSeleccion, un entrenador "es un" MiembroDeSeleccion, etc
2-Una Interface establece un "contrato" con las clases que lo implementan, y gracias a ellos se pueden definir grupos de clase con comportamientos estandarizados. Además, una clase puede implementar más de una Interface, emulando de esta forma la Herencia Multiple, ausente en java. Los métodos definidos en una Interface por default ya son públicos y abstractos, y los atributos definidos en la misma también ya son constantes "final", no hace falta ponerlo en forma explícita
3-Por último, la declaración de la lista en el último ejemplo está incorrecta, no se podrá hacer "casting" automático si se declara de la forma como está:
public static ArrayList integrantes = new ArrayList();
… debe ser así como está en la entrada del Polimorfismo I:
public static ArrayList<SeleccionFutbol> integrantes = new ArrayList<SeleccionFutbol>();
Muchas gracias por la voluntad de enseñar, que es un don. Exitos, y no dejes de iluminar el mundo con tus conocimientos tan valiosos.
Prof. Miguel Flor (UAA Paraguay)
Hola, como crítica constructiva… una interfaz NO es una clase, una interfaz se asemeja a una clase abstracta, pero no es una clase, es una interfaz…
Muchas gracias por todo!
Gracias Ruben, es cierto una interfaz no es una clase 🙂
Hemos puesto «clase» para que se pudiera entender fácilmente el concepto desde la asociación de la clase, pues es lo primero que se suele aprender cuando se trata con objetos. Consideramos que para entender el concepto de interfaz de una manera sencilla hay que desmenuzar lo que se entendía anteriormente como clase, pasar por la calse abstracta y llegar a lo que es una interfaz como tal (ir viendo que hace cada cosa gradualmente). Esto no quita lo que nos comentas: una interfaz efectivamente no es una clase.
Hola
Disculpen, en este código de la seleccion de futbol, se pueden realizar excepciones? de ser así donde se pudieran poner?, disculpen mi ignorancia soy principiante, agradezco de antemano su respuesta.
Saludos!!
y en caso de ser al revez?? la seleccion de futbol tiene un futbolista, y no un futbolista tiene una seleccion.
Muy buen tutorial, me gusto y me servio demaciado para entender este pilar de la POO
Muy buena pagina, me ayudo mucho para entender este tema!!
Saludos y Gracias
Estimados:
Ante todo, excelente página! Gracias a ustedes he aprendido bastante. Sin embargo, tengo una crítica constructiva: deberían agregar una clase, por ejemplo, «Cabaret», con un método muy diferente, algo así como «irDeJoda», de tal forma que el mismo sea aplicado a través de la interface «IntegranteSeleccionFutbol», ya que de esta forma sería más intuitivo el funcionamiento de una interfaz. Les digo porqué: si en su ejemplo simplemente quitamos el «implements IntegranteSeleccionFutbol» de la clase «SeleccionFutbol», el programa devolverá exactamente el mismo resultado. No así si se agrega lo que yo propongo.
Saludos y éxitos!
Agustín
Muy esclarecedor, gracias chicos
Gracias, me sirvió de mucho para comprender.
muy interesante y aclaratorio, de verdad me sirvió.
Muy didáctico el problema, gracias.
hola, muy bueno el aporte se les agradece.