Polimorfismo y herencia en Java

Según la leyenda Venkat Subramaniam, el polimorfismo es el concepto más importante en la programación orientada a objetos. El polimorfismo, o la capacidad de un objeto para ejecutar acciones especializadas según su tipo, es lo que hace que el código Java sea flexible. Patrones de diseño como Command, Observer, Decorator, Strategy y muchos otros creados por Gang Of Four, todos usan alguna forma de polimorfismo. Dominar este concepto mejora enormemente su capacidad para pensar en soluciones a los desafíos de programación.

Obtener el código

Puede obtener el código fuente para este desafío y ejecutar sus propias pruebas aquí: //github.com/rafadelnero/javaworld-challengers

Interfaces y herencia en polimorfismo

Con este Java Challenger, nos centramos en la relación entre polimorfismo y herencia. Lo principal a tener en cuenta es que el polimorfismo requiere herencia o implementación de interfaz . Puede ver esto en el siguiente ejemplo, con Duke y Juggy:

 public abstract class JavaMascot { public abstract void executeAction(); } public class Duke extends JavaMascot { @Override public void executeAction() { System.out.println("Punch!"); } } public class Juggy extends JavaMascot { @Override public void executeAction() { System.out.println("Fly!"); } } public class JavaMascotTest { public static void main(String... args) { JavaMascot dukeMascot = new Duke(); JavaMascot juggyMascot = new Juggy(); dukeMascot.executeAction(); juggyMascot.executeAction(); } } 

La salida de este código será:

 Punch! Fly! 

Debido a sus implementaciones específicas, tanto Dukey Juggyacciones 's va a ser ejecutado.

¿El método sobrecarga el polimorfismo?

Muchos programadores están confundidos acerca de la relación del polimorfismo con la anulación de métodos y la sobrecarga de métodos. De hecho, el único método que sobrescribe es el verdadero polimorfismo. La sobrecarga comparte el mismo nombre del método, pero los parámetros son diferentes. El polimorfismo es un término amplio, por lo que siempre habrá discusiones sobre este tema.

¿Cuál es el propósito del polimorfismo?

La gran ventaja y el propósito de usar polimorfismo es desacoplar la clase de cliente del código de implementación. En lugar de estar codificada, la clase de cliente recibe la implementación para ejecutar la acción necesaria. De esta manera, la clase cliente sabe lo suficiente para ejecutar sus acciones, lo cual es un ejemplo de acoplamiento flexible.

Para comprender mejor el propósito del polimorfismo, eche un vistazo a SweetCreator:

 public abstract class SweetProducer { public abstract void produceSweet(); } public class CakeProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cake produced"); } } public class ChocolateProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Chocolate produced"); } } public class CookieProducer extends SweetProducer { @Override public void produceSweet() { System.out.println("Cookie produced"); } } public class SweetCreator { private List sweetProducer; public SweetCreator(List sweetProducer) { this.sweetProducer = sweetProducer; } public void createSweets() { sweetProducer.forEach(sweet -> sweet.produceSweet()); } } public class SweetCreatorTest { public static void main(String... args) { SweetCreator sweetCreator = new SweetCreator(Arrays.asList(new CakeProducer(), new ChocolateProducer(), new CookieProducer())); sweetCreator.createSweets(); } } 

En este ejemplo, puede ver que la SweetCreatorclase solo conoce la  SweetProducer clase. No conoce la implementación de cada uno Sweet. Esa separación nos da flexibilidad para actualizar y reutilizar nuestras clases y hace que el código sea mucho más fácil de mantener. Al diseñar su código, siempre busque formas de hacerlo lo más flexible y fácil de mantener. El polimorfismo es una técnica muy poderosa para utilizar con estos fines.

Consejo : la @Overrideanotación obliga al programador a utilizar la misma firma de método que debe anularse. Si el método no se reemplaza, habrá un error de compilación.

Tipos de retorno covariantes en la invalidación de métodos

Es posible cambiar el tipo de retorno de un método anulado si es un tipo covariante. Un tipo covariante es básicamente una subclase del tipo de retorno. Considere un ejemplo:

 public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } } 

Debido a que Dukees a JavaMascot, podemos cambiar el tipo de retorno al anular.

Polimorfismo con las clases centrales de Java

Usamos polimorfismo todo el tiempo en las clases centrales de Java. Un ejemplo muy simple es cuando instanciamos la ArrayListclase declarando la   Listinterfaz como un tipo:

 List list = new ArrayList(); 

Para ir más allá, considere este ejemplo de código utilizando la API de colecciones de Java sin polimorfismo:

 public class ListActionWithoutPolymorphism { // Example without polymorphism void executeVectorActions(Vector vector) {/* Code repetition here*/} void executeArrayListActions(ArrayList arrayList) {/*Code repetition here*/} void executeLinkedListActions(LinkedList linkedList) {/* Code repetition here*/} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList copyOnWriteArrayList) { /* Code repetition here*/} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector()); listAction.executeArrayListActions(new ArrayList()); listAction.executeLinkedListActions(new LinkedList()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList()); } 

Código feo, ¿no? ¡Imagínese tratando de mantenerlo! Ahora mira el mismo ejemplo con polimorfismo:

 public static void main(String … polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List list) { // Execute actions with different lists } } public class ListActionInvoker { public static void main(String... masterPolymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(new Vector()); listAction.executeListActions(new ArrayList()); listAction.executeListActions(new LinkedList()); listAction.executeListActions(new CopyOnWriteArrayList()); } } 

El beneficio del polimorfismo es la flexibilidad y la extensibilidad. En lugar de crear varios métodos diferentes, podemos declarar solo un método que recibe el Listtipo genérico .

Invocar métodos específicos en una llamada a un método polimórfico

Es posible invocar métodos específicos en una llamada polimórfica, pero hacerlo tiene un costo de flexibilidad. He aquí un ejemplo:

 public abstract class MetalGearCharacter { abstract void useWeapon(String weapon); } public class BigBoss extends MetalGearCharacter { @Override void useWeapon(String weapon) { System.out.println("Big Boss is using a " + weapon); } void giveOrderToTheArmy(String orderMessage) { System.out.println(orderMessage); } } public class SolidSnake extends MetalGearCharacter { void useWeapon(String weapon) { System.out.println("Solid Snake is using a " + weapon); } } public class UseSpecificMethod { public static void executeActionWith(MetalGearCharacter metalGearCharacter) { metalGearCharacter.useWeapon("SOCOM"); // The below line wouldn't work // metalGearCharacter.giveOrderToTheArmy("Attack!"); if (metalGearCharacter instanceof BigBoss) { ((BigBoss) metalGearCharacter).giveOrderToTheArmy("Attack!"); } } public static void main(String... specificPolymorphismInvocation) { executeActionWith(new SolidSnake()); executeActionWith(new BigBoss()); } } 

La técnica que usamos aquí es la conversión , o cambiar deliberadamente el tipo de objeto en tiempo de ejecución.

Tenga en cuenta que es posible invocar un método específico solo cuando se convierte el tipo genérico en el tipo específico. Una buena analogía sería decirle explícitamente al compilador: "Oye, sé lo que estoy haciendo aquí, así que voy a convertir el objeto a un tipo específico y usar un método específico".  

En referencia al ejemplo anterior, hay una razón importante por la que el compilador se niega a aceptar la invocación de un método específico: la clase que se está pasando podría ser SolidSnake. En este caso, el compilador no puede asegurarse de que cada subclase de MetalGearCharactertenga el giveOrderToTheArmymétodo declarado.

La instanceofpalabra clave reservada

Preste atención a la palabra reservada instanceof. Antes de invocar el método específico, preguntamos si MetalGearCharacteres “ instanceofBigBoss. Si no fuera una BigBossinstancia, recibiríamos el siguiente mensaje de excepción:

 Exception in thread "main" java.lang.ClassCastException: com.javaworld.javachallengers.polymorphism.specificinvocation.SolidSnake cannot be cast to com.javaworld.javachallengers.polymorphism.specificinvocation.BigBoss 

La superpalabra clave reservada

¿Y si quisiéramos hacer referencia a un atributo o método de una superclase de Java? En este caso podríamos utilizar la superpalabra reservada. Por ejemplo:

 public class JavaMascot { void executeAction() { System.out.println("The Java Mascot is about to execute an action!"); } } public class Duke extends JavaMascot { @Override void executeAction() { super.executeAction(); System.out.println("Duke is going to punch!"); } public static void main(String... superReservedWord) { new Duke().executeAction(); } } 

Using the reserved word super in Duke’s executeAction method  invokes the superclass method.  We then execute the specific action from Duke. That’s why we can see both messages in the output below:

 The Java Mascot is about to execute an action! Duke is going to punch! 

Take the polymorphism challenge!

Let’s try out what you’ve learned about polymorphism and inheritance. In this challenge, you’re given a handful of methods from Matt Groening’s The Simpsons, and your challenge is to deduce what the output for each class will be. To start, analyze the following code carefully:

 public class PolymorphismChallenge { static abstract class Simpson { void talk() { System.out.println("Simpson!"); } protected void prank(String prank) { System.out.println(prank); } } static class Bart extends Simpson { String prank; Bart(String prank) { this.prank = prank; } protected void talk() { System.out.println("Eat my shorts!"); } protected void prank() { super.prank(prank); System.out.println("Knock Homer down"); } } static class Lisa extends Simpson { void talk(String toMe) { System.out.println("I love Sax!"); } } public static void main(String... doYourBest) { new Lisa().talk("Sax :)"); Simpson simpson = new Bart("D'oh"); simpson.talk(); Lisa lisa = new Lisa(); lisa.talk(); ((Bart) simpson).prank(); } } 

What do you think? What will the final output be? Don’t use an IDE to figure this out! The point is to improve your code analysis skills, so try to determine the output for yourself.

Choose your answer and you’ll be able to find the correct answer below.

 A) I love Sax! D'oh Simpson! D'oh B) Sax :) Eat my shorts! I love Sax! D'oh Knock Homer down C) Sax :) D'oh Simpson! Knock Homer down D) I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

What just happened? Understanding polymorphism

For the following method invocation:

 new Lisa().talk("Sax :)"); 

the output will be “I love Sax!” This is  because we are passing a String to the method and Lisa has the method.

For the next invocation:

 Simpson simpson = new Bart("D'oh");

simpson.talk();

The output will be "Eat my shorts!" This is because we’re instantiating  the Simpson type with Bart.

Now check this one, which is a little trickier:

 Lisa lisa = new Lisa(); lisa.talk(); 

Here, we are using method overloading with inheritance. We are not passing anything to the talk method, which is why the Simpson talk method is invoked.  In this case the output will be:

 "Simpson!" 

Here’s one more:

 ((Bart) simpson).prank(); 

In this case, the prank String was passed when we instantiated the Bart class with new Bart("D'oh");. In this case,  first the super.prank method will be invoked, followed by the specific prank method from Bart. The output will be:

 "D'oh" "Knock Homer down" 

Video challenge! Debugging Java polymorphism and inheritance

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the Java polymorphism challenge:

Common mistakes with polymorphism

It’s a common mistake to think it’s possible to invoke a specific method without using casting.

Another mistake is being unsure what method will be invoked when instantiating a class polymorphically. Remember that the method to be invoked is the method of the created instance.

Also remember that method overriding is not method overloading.

It’s impossible to override a method if the parameters are different. It is possible to change the return type of the overridden method if the return type is a subclass of the superclass method.

Que recordar sobre el polimorfismo

  • La instancia creada determinará qué método se invocará cuando se use polimorfismo.
  • La @Overrideanotación obliga al programador a utilizar un método anulado; si no, habrá un error del compilador.
  • El polimorfismo se puede utilizar con clases normales, clases abstractas e interfaces.
  • La mayoría de los patrones de diseño dependen de alguna forma de polimorfismo.
  • La única forma de utilizar un método específico en su subclase polimórfica es mediante la conversión.
  • Es posible diseñar una estructura poderosa en su código usando polimorfismo.
  • Ejecute sus pruebas. ¡Haciendo esto, podrás dominar este poderoso concepto!

Clave de respuesta

La respuesta a esta retador Java es D . La salida sería:

 I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down 

Esta historia, "Polimorfismo y herencia en Java" fue publicada originalmente por JavaWorld.