Java: polimorfismo

El polimorfismo es la capacidad de un objeto de adoptar muchas formas. El uso más común de polimorfismo en OOP ocurre cuando se usa una referencia de clase principal para hacer referencia a un objeto de clase secundaria.

Cualquier objeto Java que pueda pasar más de una prueba IS-A se considera polimórfico. En Java, todos los objetos Java son polimórficos ya que cualquier objeto pasará la prueba IS-A para su propio tipo y para la clase Object.

Es importante saber que la única forma posible de acceder a un objeto es a través de una variable de referencia. Una variable de referencia puede ser de un solo tipo. Una vez declarado, el tipo de variable de referencia no se puede cambiar.

La variable de referencia se puede reasignar a otros objetos siempre que no se declare final. El tipo de variable de referencia determinaría los métodos que puede invocar en el objeto.

Una variable de referencia puede hacer referencia a cualquier objeto de su tipo declarado o cualquier subtipo de su tipo declarado. Una variable de referencia se puede declarar como clase o tipo de interfaz.

Ejemplo

Veamos un ejemplo.

public interface Vegetarian{}
public class Animal{}
public class Deer extends Animal implements Vegetarian{}

Ahora, la clase Deer se considera polimórfica ya que tiene herencia múltiple. Lo siguiente es válido para los ejemplos anteriores:

  • Un ciervo es un animal
  • Un ciervo es vegetariano
  • Un ciervo es un ciervo
  • Un ciervo es un objeto

Cuando aplicamos los hechos de la variable de referencia a una referencia de objeto Deer, las siguientes declaraciones son legales:

Ejemplo

Deer d = new Deer();
Animal a = d;
Vegetarian v = d;
Object o = d;

Todas las variables de referencia d, a, v, o se refieren al mismo objeto Deer en el montón.

Métodos virtuales

En esta sección, le mostraré cómo el comportamiento de los métodos anulados en Java le permite aprovechar el polimorfismo al diseñar sus clases.

Ya hemos hablado de la anulación de métodos, donde una clase secundaria puede anular un método en su padre. Un método anulado está esencialmente oculto en la clase principal y no se invoca a menos que la clase secundaria utilice la palabra clave super dentro del método anulado.

Ejemplo

/* File name : 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("Constructing an Employee");
      this.name = name;
      this.address = address;
      this.number = number;
   }

   public void mailCheck() {
      System.out.println("Mailing a check to " + 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;
   }
}

Ahora suponga que ampliamos la clase de empleado de la siguiente manera:

/* File name : Salary.java */
public class Salary extends Employee {
   private double salary; // Annual salary
   
   public Salary(String name, String address, int number, double salary) {
      super(name, address, number);
      setSalary(salary);
   }
   
   public void mailCheck() {
      System.out.println("Within mailCheck of Salary class ");
      System.out.println("Mailing check to " + getName()
      + " with salary " + salary);
   }
   
   public double getSalary() {
      return salary;
   }
   
   public void setSalary(double newSalary) {
      if(newSalary >= 0.0) {
         salary = newSalary;
      }
   }
   
   public double computePay() {
      System.out.println("Computing salary pay for " + getName());
      return salary/52;
   }
}

Ahora, estudie el siguiente programa cuidadosamente e intente determinar su salida:

/* File name : VirtualDemo.java */
public class VirtualDemo {

   public static void main(String [] args) {
      Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
      Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
      System.out.println("Call mailCheck using Salary reference --");   
      s.mailCheck();
      System.out.println("\n Call mailCheck using Employee reference--");
      e.mailCheck();
   }
}

Esto producirá el siguiente resultado:

Salida

Constructing an Employee
Constructing an Employee

Call mailCheck using Salary reference --
Within mailCheck of Salary class
Mailing check to Mohd Mohtashim with salary 3600.0

Call mailCheck using Employee reference--
Within mailCheck of Salary class
Mailing check to John Adams with salary 2400.0

Aquí, instanciamos dos objetos Salario. Uno usando una referencia de salariosy el otro usando una referencia de empleado e.

Al invocar s.mailCheck () , el compilador ve mailCheck () en la clase Salario en tiempo de compilación, y la JVM invoca mailCheck () en la clase Salario en tiempo de ejecución.

mailCheck () en e es bastante diferente porque ees una referencia de empleado. Cuando el compilador ve e.mailCheck () , el compilador ve el método mailCheck () en la clase Employee.

Aquí, en tiempo de compilación, el compilador usó mailCheck () en Empleado para validar esta declaración. Sin embargo, en tiempo de ejecución, la JVM invoca mailCheck () en la clase Salario.

Este comportamiento se denomina invocación de método virtual y estos métodos se denominan métodos virtuales. Un método anulado se invoca en tiempo de ejecución, sin importar qué tipo de datos sea la referencia que se utilizó en el código fuente en tiempo de compilación.