Desafiadores Java # 3: Polimorfismo y Herencia
Continuamos traduciendo una serie de artículos con problemas de Java. La última publicación sobre las líneas causó una discusión sorprendentemente acalorada . Esperamos que no pase por este artículo tampoco. Y sí, ahora te invitamos a la transmisión del décimo aniversario de nuestro curso de Desarrollador Java .
Según el legendario 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 realizar acciones especializadas en función de su tipo, es lo que hace que el código Java sea flexible. Los patrones de diseño como el Comando, el Observador, el Decorador, la Estrategia y muchos otros creados por la Banda de los Cuatro usan alguna forma de polimorfismo. Dominar este concepto mejorará en gran medida su capacidad de pensar a través de soluciones de software.

Puede tomar el código fuente de este artículo y experimentar aquí: https://github.com/rafadelnero/javaworld-challengers
Interfaces y herencia en polimorfismo
En este artículo, nos centraremos en la relación entre polimorfismo y herencia. Lo principal a tener en cuenta es que el polimorfismo requiere herencia o implementación de una interfaz . Puedes 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(); } }
El resultado de este código será así:
Punch! Fly!
Como se definen implementaciones específicas, se Juggy
métodos de Duke
y Juggy
.
¿El método sobrecarga un polimorfismo? Muchos programadores confunden la relación del polimorfismo con la anulación y la sobrecarga . De hecho, solo redefinir el método es el verdadero polimorfismo. La sobrecarga utiliza el mismo nombre de método, pero diferentes parámetros. El polimorfismo es un término amplio, por lo que siempre habrá discusiones sobre este tema.
¿Cuál es el propósito del polimorfismo?
Una gran ventaja y propósito de usar el polimorfismo es reducir la conexión de clase de cliente con la implementación. En lugar de hardcode, la clase de cliente obtiene una implementación de la dependencia para realizar la acción necesaria. Por lo tanto, la clase de cliente sabe el mínimo para completar sus acciones, que es un ejemplo de enlace débil.
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> sweetProducer; public SweetCreator(List<SweetProducer> 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 clase SweetCreator
conoce la clase SweetProducer
. Él no conoce la implementación de cada Sweet
. Esta separación nos da la flexibilidad para actualizar y reutilizar nuestras clases, y esto hace que el código sea mucho más fácil de mantener. Al diseñar el código, busque siempre formas de hacerlo lo más flexible y conveniente posible. El polimorfismo es una forma muy poderosa de ser utilizado para este propósito.
La @Override
requiere que el programador use la misma firma de método, que debe anularse. Si el método no se anula, habrá un error de compilación.
Tipos de retorno covariantes al anular un método
Puede cambiar el tipo de retorno de un método anulado si es un tipo covariante . El tipo covariante es básicamente una subclase del valor de retorno.
Considere un ejemplo:
public abstract class JavaMascot { abstract JavaMascot getMascot(); } public class Duke extends JavaMascot { @Override Duke getMascot() { return new Duke(); } }
Como Duke
es JavaMascot
, podemos cambiar el tipo del valor de retorno al anularlo.
Polimorfismo en clases base de Java
Usamos constantemente el polimorfismo en las clases base de Java. Un ejemplo muy simple es crear una instancia de una clase ArrayList
con una declaración de tipo como una interfaz de List
.
List<String> list = new ArrayList<>();
Considere un código de muestra que usa la API de colecciones de Java sin polimorfismo:
public class ListActionWithoutPolymorphism {
Código desagradable, ¿verdad? ¡Imagina que necesitas acompañarlo! Ahora considere el mismo ejemplo con polimorfismo:
public static void main(String... polymorphism) { ListAction listAction = new ListAction(); listAction.executeListActions(); } public class ListAction { void executeListActions(List<Object> list) {
La ventaja del polimorfismo es la flexibilidad y la extensibilidad. En lugar de crear varios métodos diferentes, podemos declarar un método que obtenga un tipo de List
.
Llamar a métodos específicos para un método polimórfico
Puede llamar a métodos específicos con una llamada de método polimórfico, esto se debe a la flexibilidad. Aquí hay 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");
La técnica que usamos aquí es lanzar o cambiar conscientemente el tipo de un objeto en tiempo de ejecución.
Tenga en cuenta que una llamada a un método específico solo es posible cuando se convierte un tipo más general a un tipo más 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 en un tipo específico y utilizaré este método".
En referencia al ejemplo anterior, el compilador tiene una buena razón para no aceptar la llamada de ciertos métodos: la clase que se pasa debe ser SolidSnake
. En este caso, el compilador no tiene forma de garantizar que cada subclase de MetalGearCharacter
tenga un método giveOrderToTheArmy
.
Instancia de palabra clave
Tenga en cuenta la palabra reservada instanceof
. Antes de llamar a un método específico, preguntamos si MetalGearCharacter
instancia de BigBoss
. Si no se trata de una instancia de BigBoss
, obtendremos la siguiente 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
Palabra clave super
¿Qué pasa si queremos referirnos a un atributo o método de la clase padre? En este caso, podemos usar la palabra clave super
.
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(); } }
El uso de la palabra reservada super
en el método executeAction
de la clase Duke
llama al método de la clase padre. Luego realizamos una acción específica de la clase Duke
. Es por eso que podemos ver ambos mensajes en la salida:
The Java Mascot is about to execute an action! Duke is going to punch!
Resolver el problema del polimorfismo.
Veamos qué aprendiste sobre el polimorfismo y la herencia.
En este problema, se le dan varios métodos de Los Simpson de Matt Groening, de los Wavs se le pide que desvele cuál será la conclusión para cada clase. Primero, analice cuidadosamente el siguiente código:
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(); } }
Que piensas ¿Cuál será el resultado? ¡No use el IDE para averiguarlo! El objetivo es mejorar tus habilidades de análisis de código, así que trata de decidir por ti mismo.
Elija su respuesta (puede encontrar la respuesta correcta al final del artículo).
A)
¡Amo a Sax!
D'oh
Simpson!
D'oh
B)
Sax :)
¡Come mis pantalones cortos!
¡Amo a Sax!
D'oh
Derribar jonrón
C)
Sax :)
D'oh
Simpson!
Derribar jonrón
D)
¡Amo a Sax!
¡Come mis pantalones cortos!
Simpson!
D'oh
Derribar jonrón
Que paso Comprender el polimorfismo
Para el siguiente método llame:
new Lisa().talk("Sax :)");
la salida será "¡Amo a Sax!". Esto se debe a que pasamos la cadena al método y la clase Lisa
tiene dicho método.
Para la próxima llamada:
Simpson simpson = new Bart("D'oh"); simpson.talk();
La salida será "¡Come mis pantalones cortos!". Esto se debe a que inicializamos el tipo Simpson
con Bart
.
Ahora mira, esto es un poco más complicado:
Lisa lisa = new Lisa(); lisa.talk();
Aquí usamos el método de sobrecarga con herencia. No pasamos nada al método de talk
, por lo que Simpson
llama al método de talk
de Simpson
.
En este caso, la salida será "¡Simpson!".
Aquí hay otro:
((Bart) simpson).prank();
En este caso, la cadena de prank
se pasó al crear instancias de la clase Bart
través del new Bart("D'oh");
. En este caso, super.prank()
se llama al método super.prank()
, y luego se llama al método super.prank()
de la clase Bart
. La conclusión será:
"D'oh" "Knock Homer down"
Errores comunes de polimorfismo
Un error común es pensar que puede llamar a un método específico sin utilizar tipos de conversión.
Otro error es la incertidumbre acerca de qué método se llamará al crear instancias polimórficas de la clase. Recuerde que el método que se llama es el método de la instancia instanciada.
Recuerde también que anular un método no es una sobrecarga de un método.
No se puede anular el método si los parámetros son diferentes. Puede cambiar el tipo de retorno de un método anulado si el tipo de retorno es una subclase.
Lo que debes recordar sobre el polimorfismo
La instancia creada determina qué método se llamará cuando se usa el polimorfismo.
La @Override
requiere que el programador use un método anulado; de lo contrario, se producirá un error del compilador.
El polimorfismo se puede usar con clases regulares, clases abstractas e interfaces.
La mayoría de los patrones de diseño dependen de alguna forma de polimorfismo.
La única forma de llamar a su método en una subclase polimórfica es usar la conversión de tipos.
Puede crear una estructura de código potente utilizando polimorfismo.
Experimento ¡A través de esto, podrás dominar este poderoso concepto!
La respuesta
La respuesta es D.
La conclusión será:
I love Sax! Eat my shorts! Simpson! D'oh Knock Homer down
Como siempre, agradezco sus comentarios y preguntas. Y estamos esperando a Vitaly en una lección abierta .