Tutorial de polimorfismo en Java: con código de ejemplo de programación orientada a objetos

El polimorfismo permite tratar los objetos de forma sustituible. Esto reduce la duplicación de código cuando desea que se realicen las mismas acciones en diferentes tipos de objetos. Polimorfismo significa literalmente " muchas formas ".

Expliquemos lo que queremos decir con esto exactamente.

Explicación del polimorfismo por analogía

Si alguna vez ha viajado internacionalmente, es probable que un elemento en su lista de verificación de empaque sea un adaptador de enchufe eléctrico. De lo contrario, es posible que no pueda cargar su teléfono y otros dispositivos.

embalaje.jpg

Curiosamente, hay aproximadamente 16 tipos diferentes de enchufes eléctricos en todo el mundo. Algunos tienen 2 pines, algunos tienen 3 pines, algunos pines son circulares, algunos pines son rectangulares y la configuración de los pines varía.

La solución que toma la mayoría de la gente es comprar un adaptador de enchufe universal.

Para ver el problema de otra manera, generalmente el problema es que tenemos una interfaz de enchufe que acepta solo 1 tipo de objeto de enchufe. Los zócalos no son polimórficos.

La vida sería mucho más fácil para todos si tuviéramos enchufes que pudieran aceptar muchos tipos diferentes de enchufes. Podemos hacer que la interfaz del encaje sea polimórfica creando ranuras de diferentes formas. Puedes ver en la imagen de abajo cómo se ha hecho esto.

metáfora del enchufe

El polimorfismo nos ayuda a crear interfaces más universales.

Explicación con código

Cualquier objeto que tenga una relación IS-A se considera polimórfico. Tiene una relación IS-A a través de la herencia (usando la palabra clave extiende en la firma de clase), o mediante interfaces (usando la palabra clave implements en la firma de clase).

Para comprender completamente el polimorfismo, también debe comprender la herencia y las interfaces.

class Dog extends Animal implements Canine{ // ... some code here } 

Basado en el fragmento anterior, una Dogtiene las siguientes relaciones IS-A: Animal, Canine, y Object(cada clase implícitamente hereda de la clase Object, que suena un poco ridículo!).

Démosle un ejemplo simple (tonto) para ilustrar cómo podemos usar el polimorfismo para simplificar nuestro código. Queremos crear una aplicación con un interrogador que pueda convencer a cualquier animal para que hable.

interrogatorio

Crearemos una Interrogatorclase que se encargue de convencer a los animales para que hablen. No queremos escribir un método para cada tipo de animal: convinceDogToTalk(Dog dog), convinceCatToTalk(Cat cat)y así sucesivamente.

Preferiríamos un método general que aceptara cualquier animal. ¿Cómo podemos hacer esto?

class Interrogator{ public static void convinceToTalk(Animal subject) { subject.talk(); } } // We don't want anyone creating an animal object! abstract class Animal { public abstract void talk(); } class Dog extends Animal { public void talk() { System.out.println("Woof!"); } } class Cat extends Animal { public void talk() { System.out.println("Meow!"); } } public class App { public static void main(String[] args){ Dog dog = new Dog(); Cat cat = new Cat(); Animal animal = new Dog(); Interrogator.convinceToTalk(dog); //prints "Woof!" Interrogator.convinceToTalk(cat); //prints "Meow!" Interrogator.convinceToTalk(animal); //prints "Woof!" } } 

Creamos el convinceToTalkmétodo para aceptar un Animalobjeto como parámetro. Dentro del método llamamos al talkmétodo de ese objeto. Siempre que el tipo de objeto sea Animaluna subclase de Animal, el compilador está contento.

La máquina virtual Java (JVM) decide en tiempo de ejecución qué método se llamará en función de la clase del objeto. Si el objeto tiene un tipo de Dog, la JVM invoca la implementación que dice "¡Guau!".

Esto vale la pena de 2 maneras:

  1. Solo necesitamos escribir un método general. No necesitamos hacer ninguna verificación de tipo.
  2. En el futuro, si creamos un nuevo tipo de animal, no es necesario que modifiquemos la Interrogatorclase.

Este tipo de polimorfismo se denomina predominante.

Primordial

El ejemplo que discutimos ya cubrió el concepto amplio de anulación. Demos una definición formal y más específicos.

La anulación es cuando crea una implementación diferente del mismo método de instancia exacto (firma de método idéntica) en una clase relacionada.

En tiempo de ejecución, se elige el método del tipo de objeto . Esta es la razón por la que la anulación también se conoce como polimorfismo en tiempo de ejecución.

La invalidación se logra proporcionando una implementación diferente de un método en una clase secundaria (subclase), que se define en su clase principal (superclase).

herencia primordial

La anulación también se logra proporcionando diferentes implementaciones de un método definido en una interfaz.

interfaz predominante

Reglas para anular un método:

  1. Debe ser un método definido a través de una relación IS-A (a través de extendso implements). Es por eso que puede encontrarlo como polimorfismo de subtipo.
  2. Debe tener la misma lista de argumentos que la definición del método original.
  3. Debe tener el mismo tipo de retorno o un tipo de retorno que sea una subclase del tipo de retorno de la definición del método original.
  4. No puede tener un modificador de acceso más restrictivo.
  5. Puede tener un modificador de acceso menos restrictivo.
  6. Debe no lanzar una excepción nuevo o revisado más amplio.
  7. Puede generar excepciones más limitadas , menos o ninguna, por ejemplo, un método que declara una IOException puede ser anulado por un método que declara una FileNotFoundException (porque es una subclase de IOException ).
  8. El método reemplazado puede lanzar cualquier excepción no verificada, independientemente de si el método reemplazado declara la excepción.

Recomendación: use la anotación @override cuando anule métodos. Proporciona comprobación de errores en tiempo de compilación en la firma del método. Esto le ayudará a evitar romper las reglas enumeradas anteriormente.

anular anotación

Prohibir anular

Si no desea que se anule un método, declarelo como final.

class Account { public final void withdraw(double amount) { double newBalance = balance - amount; if(newBalance > 0){ balance = newBalance; } } } 

Métodos estáticos

No puede anular un método estático . Realmente está creando una definición independiente del método en una clase relacionada.

class A { public static void print() { System.out.println("in A"); } } class B extends A { public static void print() { System.out.println("in B"); } } class Test { public static void main(String[] args) { A myObject = new B(); myObject.print(); // prints “in A” } } 

Ejecutar la Testclase en el ejemplo anterior imprimirá "en A". Esto demuestra que la anulación no está sucediendo aquí.

Si cambia el printmétodo en las clases Ay Bdesea ser un método de instancia quitando staticde la firma del método, y ejecuta la Testclase nuevamente, ¡imprimirá "en B" en su lugar! La anulación está sucediendo ahora.

Recuerde, la invalidación elige el método según el tipo de objeto, no el tipo de variable. ?

Sobrecarga (polimorfismo funcional)

La sobrecarga es cuando creas diferentes versiones del mismo método.

El nombre del método debe ser el mismo, pero podemos cambiar los parámetros.

y tipo de retorno.

In Java's Math class, you will find many examples of overloaded methods. The max method is overloaded for different types. In all cases, it is returning the number with the highest value from the 2 values provided, but it does it for different (unrelated) number types.

overloading-max-example

The (reference) variable type is what determines which overloaded method will be chosen. Overloading is done at compile time.

Overloaded methods provide more flexibility for people using your class. People using your class may have data in different formats, or may have different data available to them depending on different situations in their application.

For example, the List class overloads the remove method. A List is an ordered collection of objects. So, you may want to remove an object at a particular position (index) in a list. Or you may not know the position, and just want to remove the object wherever it is. So that's why it has 2 versions.

métodos de lista sobrecargados

Constructors can be overloaded also.

For example, the Scanner class has many different inputs that can be provided for creating an object. Below is a small snapshot of the constructors that cater to this.

constructor

Rules for overloading a method:

  1. It must have a different argument list.
  2. It may have a different return type.
  3. It may have different access modifiers.
  4. It may throw different exceptions.
  5. Methods from a superclass can be overloaded in a subclass.

Differences between overriding and overloading

  1. Overriding must be based on a method from an IS-A relationship, overloading doesn't have to be. Overloading can occur within a single class.
  2. Overridden methods are chosen based on the object type, whereas overloaded methods are chosen based on the (reference) variable type.
  3. Overriding occurs at run-time, while overloading occurs at compile-time.

Parametric polymorphism

Parameteric polymorphism is achieved through generics in Java.

Generics were added to the language in version 5.0. They were designed to extend Java's type system to allow "a type or method to operate on objects of various types while providing compile-time type safety".

Basically, a generic form of a class or method can have all of its types replaced.

A simple example is ArrayList. The class definition has a generic in it, and it is signified by . Some of the instance methods such as add use this generic type in their signatures.

definición de clase de lista de arrays

Arraylist definición agregar métodos

By providing a type in angle brackets when we create an ArrayList object, we fill in the generic references defined throughout the class. So, if we create an ArrayList with the Dog generic type, the add method will only accept a Dog object as an argument.

firma del método del perro de lista de arrays

There is a compile-time error if you try to add anything other than a Dog! If you use a code editor such as IntelliJ, you will get the red squiggly line to highlight your offense (as below).

comprobación de tipo de lista de matrices

Final Words

El polimorfismo es un tema complicado de abordar, especialmente cuando eres nuevo en la programación. Se necesita algo de tiempo para identificar las situaciones adecuadas para usarlo en su código.

Pero una vez que se sienta cómodo con él, encontrará que mejora mucho su código.

Atribución de foto

Foto de portada de Markus Spiske en Unsplash.