Desafiadores Java # 3: Polimorfismo y Herencia

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 { //    void executeVectorActions(Vector<Object> vector) {/*    */} void executeArrayListActions(ArrayList<Object> arrayList) {/*    */} void executeLinkedListActions(LinkedList<Object> linkedList) {/*    */} void executeCopyOnWriteArrayListActions(CopyOnWriteArrayList<Object> copyOnWriteArrayList) { /*    */} } public class ListActionInvokerWithoutPolymorphism { listAction.executeVectorActions(new Vector<>()); listAction.executeArrayListActions(new ArrayList<>()); listAction.executeLinkedListActions(new LinkedList<>()); listAction.executeCopyOnWriteArrayListActions(new CopyOnWriteArrayList<>()); } 

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) { //      } } 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<>()); } } 

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"); //      // 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 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 .

Source: https://habr.com/ru/post/es429120/


All Articles