Polimorfismo en Java


El polimorfismo es la capacidad de un mismo comportamiento de tener múltiples formas o manifestaciones diferentes.

El polimorfismo es que la misma interfaz, al usar diferentes instancias, ejecuta diferentes operaciones, como se muestra en la figura:

El polimorfismo es la manifestación de las múltiples formas de un objeto.

En la vida real, por ejemplo, la acción de presionar la tecla F1:

  • Si estás en la interfaz de Flash, aparece la documentación de ayuda de AS 3;
  • Si estás en Word, aparece la ayuda de Word;
  • Si estás en Windows, aparece la ayuda y el soporte de Windows.

El mismo evento que ocurre en diferentes objetos produce diferentes resultados.

Ventajas del polimorfismo

  • 1. Eliminar el acoplamiento entre tipos
  • 2. Sustituibilidad
  • 3. Extensibilidad
  • 4. Capacidad de interfaz
  • 5. Flexibilidad
  • 6. Simplicidad

Tres condiciones necesarias para que exista el polimorfismo

  • Herencia
  • Sobrescritura
  • Una referencia de la clase padre apunta a un objeto de la clase hija:Parent p = new Child();

class Shape {
    void draw() {}
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Circle.draw()");
    }
}
 
class Square extends Shape {
    void draw() {
        System.out.println("Square.draw()");
    }
}
 
class Triangle extends Shape {
    void draw() {
        System.out.println("Triangle.draw()");
    }
}

Cuando se llama a un método mediante polimorfismo, primero se verifica si la clase padre tiene ese método; si no lo tiene, se produce un error de compilación; si lo tiene, se llama al método del mismo nombre en la clase hija.

Los beneficios del polimorfismo: permite que el programa tenga una buena extensibilidad y pueda procesar de manera genérica los objetos de todas las clases.

A continuación se muestra la demostración de un ejemplo de polimorfismo; para más detalles, consulte los comentarios:

Código del archivo Test.java:

public class Test { public static void main(String[] args) { show(new Cat()); //Llamar al método show con un objeto Cat show(new Dog()); //Llamar al método show con un objeto Dog Animal a = new Cat(); //Conversión ascendente (upcasting) a.eat(); //Se llama al eat de Cat Cat c = (Cat)a; //Conversión descendente (downcasting) c.work(); //Se llama al work de Cat } public static void show(Animal a) { a.eat(); //Comprobación de tipo if (a instanceof Cat) { //Lo que hace un gato Cat c = (Cat)a; c.work(); } else if (a instanceof Dog) { //Lo que hace un perro Dog c = (Dog)a; c.work(); } } } abstract class Animal { abstract void eat(); } class Cat extends Animal { public void eat() { System.out.println("Comer pescado"); } public void work() { System.out.println("Cazar ratones"); } } class Dog extends Animal { public void eat() { System.out.println("Comer huesos"); } public void work() { System.out.println("Cuidar la casa"); } }

Al ejecutar el programa anterior, el resultado es:

Comer pescado

Funciones virtuales

Las funciones virtuales existen para el polimorfismo.

En Java, en realidad no existe el concepto de función virtual; sus funciones normales equivalen a las funciones virtuales de C++. La vinculación dinámica es el comportamiento predeterminado de Java. Si en Java no se desea que una función tenga características de función virtual, se puede añadir la palabra clave final para convertirla en una función no virtual.

Sobrescritura

Vamos a explicar cómo, en Java, al diseñar clases, el comportamiento de los métodos sobrescritos afecta al polimorfismo.

Ya hemos hablado de la sobrescritura de métodos, es decir, que una subclase puede sobrescribir los métodos de la clase padre.

Cuando un objeto de la subclase llama a un método sobrescrito, se llama al método de la subclase, no al método sobrescrito de la clase padre.

Para llamar al método sobrescrito de la clase padre, se debe utilizar la palabra clavesuper。

Código del archivo Employee.java:

/*Nombre de archivo: Employee.java*/ public class Employee { private String name; private String address; private int number; public Employee(String name, String address, int number) { System.out.println("Constructor de Employee"); this.name = name; this.address = address; this.number = number; } public void mailCheck() { System.out.println("Enviar cheque a:" + this.name + " " + this.address); } public String toString() { return name + " " + address + " " + number; } public String getName() { return name; } public String getAddress() { return address; } public void setAddress(String newAddress) { address = newAddress; } public int getNumber() { return number; } }

Supongamos que la siguiente clase hereda de la clase Employee:

Código del archivo Salary.java:

/*Nombre de archivo: Salary.java*/ public class Salary extends Employee { private double salary; //Salario anual public Salary(String name, String address, int number, double salary) { super(name, address, number); setSalary(salary); } public void mailCheck() { System.out.println("Método mailCheck de la clase Salary"); System.out.println("Enviar cheque a:" + getName() + ", el salario es:" + salary); } public double getSalary() { return salary; } public void setSalary(double newSalary) { if(newSalary >= 0.0) { salary = newSalary; } } public double computePay() { System.out.println("Calcular el salario, pagar a:" + getName()); return salary/52; } }

Ahora leamos detenidamente el siguiente código e intentemos dar su resultado de salida:

Código del archivo VirtualDemo.java:

/*Nombre de archivo: VirtualDemo.java*/ public class VirtualDemo { public static void main(String [] args) { Salary s = new Salary("Empleado A", "Pekín", 3, 3600.00); Employee e = new Salary("Empleado B", "Shanghái", 2, 2400.00); System.out.println("Llamar a mailCheck usando la referencia de Salary --"); s.mailCheck(); System.out.println("\nLlamar a mailCheck usando la referencia de Employee --"); e.mailCheck(); } }

El resultado de compilar y ejecutar el ejemplo anterior es el siguiente:

Employee 构造函数
Employee 构造函数
使用 Salary 的引用调用 mailCheck -- 
Salary 类的 mailCheck 方法 
邮寄支票给:员工 A ,工资为:3600.0

使用 Employee 的引用调用 mailCheck--
Salary 类的 mailCheck 方法 
邮寄支票给:员工 B ,工资为:2400.0

Análisis del ejemplo

  • En el ejemplo, se instanciaron dos objetos Salary: uno usando la referencia Salary s, y otro usando la referencia Employee e.

  • Cuando se llama a s.mailCheck(), el compilador encuentra mailCheck() en la clase Salary durante la compilación, y durante la ejecución la JVM llama a mailCheck() de la clase Salary.

  • e es una referencia de Employee, pero la referencia e finalmente ejecuta el método mailCheck() de la clase Salary.

  • En tiempo de compilación, el compilador usa el método mailCheck() de la clase Employee para verificar la sentencia, pero en tiempo de ejecución, la Máquina Virtual de Java (JVM) llama al método mailCheck() de la clase Salary.

Todo el proceso anterior se denomina invocación de método virtual, y dicho método se denomina método virtual.

En Java, todos los métodos pueden comportarse de esta manera; por lo tanto, los métodos sobrescritos pueden invocarse en tiempo de ejecución, sin importar el tipo de dato de la variable de referencia en el código fuente en el momento de la compilación.


Formas de implementar el polimorfismo

Forma 1: Sobrescritura:

Este contenido ya se explicó detalladamente en el capítulo anterior, así que no se repetirá; para más detalles, visita:Java Sobrescritura (Override) y Sobrecarga (Overload)。

Forma 2: Interfaces

  • 1. El ejemplo más representativo de interfaz en la vida cotidiana es el enchufe. Por ejemplo, un enchufe de tres clavijas puede conectarse a un tomacorriente de tres orificios, porque cada país tiene sus propias reglas de interfaz. Puede que en el extranjero no funcione, porque allí se definen sus propios tipos de interfaz.

  • 2. La interfaz en Java es similar a la interfaz de la vida cotidiana; es una colección de características de métodos, pero sin la implementación de los métodos. Puedes consultar específicamenteinterfaces en Javael contenido de este capítulo.

Forma 3: Clases abstractas y métodos abstractos

Para más detalles, consultaClases abstractas en Javael capítulo.

Otras extensiones