Java - Polimorfismo

Polimorfismo é a capacidade de um objeto de assumir várias formas. O uso mais comum de polimorfismo em OOP ocorre quando uma referência de classe pai é usada para se referir a um objeto de classe filho.

Qualquer objeto Java que pode passar em mais de um teste IS-A é considerado polimórfico. Em Java, todos os objetos Java são polimórficos, pois qualquer objeto passará no teste IS-A para seu próprio tipo e para a classe Object.

É importante saber que a única forma possível de acessar um objeto é por meio de uma variável de referência. Uma variável de referência pode ser de apenas um tipo. Uma vez declarado, o tipo de uma variável de referência não pode ser alterado.

A variável de referência pode ser reatribuída a outros objetos, desde que não seja declarada final. O tipo da variável de referência determinaria os métodos que ela pode invocar no objeto.

Uma variável de referência pode se referir a qualquer objeto de seu tipo declarado ou qualquer subtipo de seu tipo declarado. Uma variável de referência pode ser declarada como uma classe ou tipo de interface.

Exemplo

Vamos ver um exemplo.

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

Agora, a classe Deer é considerada polimórfica, pois possui herança múltipla. A seguir são verdadeiros para os exemplos acima -

  • Um cervo é um animal
  • A Deer IS-A Vegetarian
  • A Deer IS-A Deer
  • Um cervo é um objeto

Quando aplicamos a variável de referência fatos a uma referência de objeto Deer, as seguintes declarações são legais -

Exemplo

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

Todas as variáveis ​​de referência d, a, v, o referem-se ao mesmo objeto Deer na pilha.

Métodos Virtuais

Nesta seção, mostrarei como o comportamento dos métodos substituídos em Java permite que você aproveite o polimorfismo ao projetar suas classes.

Já discutimos a substituição de método, onde uma classe filha pode substituir um método em seu pai. Um método sobrescrito está essencialmente oculto na classe pai e não é invocado a menos que a classe filha use a palavra-chave super no método de sobrescrita.

Exemplo

/* 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;
   }
}

Agora, suponha que estendamos a classe Employee da seguinte forma -

/* 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;
   }
}

Agora, você estuda o seguinte programa cuidadosamente e tenta determinar sua saída -

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

Isso produzirá o seguinte resultado -

Resultado

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

Aqui, instanciamos dois objetos Salário. Um usando uma referência de salários, e o outro usando uma referência de funcionário e.

Ao invocar s.mailCheck () , o compilador vê mailCheck () na classe Salary em tempo de compilação e a JVM chama mailCheck () na classe Salary em tempo de execução.

mailCheck () em e é bem diferente porque eé uma referência do funcionário. Quando o compilador vê e.mailCheck () , o compilador vê o método mailCheck () na classe Employee.

Aqui, em tempo de compilação, o compilador usou mailCheck () em Employee para validar esta declaração. No tempo de execução, entretanto, a JVM chama mailCheck () na classe Salary.

Esse comportamento é conhecido como invocação de método virtual e esses métodos são chamados de métodos virtuais. Um método sobrescrito é invocado em tempo de execução, não importa o tipo de dado da referência que foi usado no código-fonte em tempo de compilação.