Java 多态

多态性是对象具有多种形式的能力。 OOP 中最常见的多态性使用发生在使用父类引用来引用子类对象时。

任何可以通过多个 IS-A 测试的 Java 对象都被认为是多态的。 在 Java 中,所有 Java 对象都是多态的,因为任何对象都将通过其自身类型和 Object 类的 IS-A 测试。

重要的是要知道访问对象的唯一可能方法是通过引用变量。 引用变量只能是一种类型。 一旦声明,引用变量的类型就不能改变。

引用变量可以重新分配给其他对象,前提是它没有声明为 final。 引用变量的类型将决定它可以在对象上调用的方法。

引用变量可以引用其声明类型的任何对象或其声明类型的任何子类型。 引用变量可以声明为类或接口类型。

让我们看一个例子。

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

现在,Deer 类被认为是多态的,因为它具有多重继承。 对于上述示例,下面说法是正确的

  • 一个 Deer IS-A Animal
  • 一个 Deer IS-A Vegetarian
  • 一个 Deer IS-A Deer
  • 一个 Deer IS-A Object

当我们将引用变量事实应用于 Deer 对象引用时,以下声明是合法的

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

所有的引用变量 d、a、v、o 都指向堆中的同一个 Deer 对象。

虚拟方法

在本节中,将展示 Java 中重写方法的行为如何让我们在设计类时利用多态性。

我们已经讨论过方法重写,子类可以重写其父类中的方法。 被重写的方法本质上隐藏在父类中,除非子类在重写方法中使用 super 关键字,否则不会被调用。

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

现在假设我们继承 Employee 类,如下所示

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

现在,仔细研究以下程序并尝试确定其输出结果

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

这将产生以下结果

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

在这里,我们实例化了两个 Salary 对象。 一个使用 Salary 引用 s,另一个使用 Employee 引用 e。

在调用 s.mailCheck() 时,编译器在编译时看到 Salary 类中的 mailCheck(),而 JVM 在运行时调用 Salary 类中的 mailCheck()。

e 上的 mailCheck() 完全不同,因为 e 是 Employee 引用。 当编译器看到 e.mailCheck() 时,编译器会看到 Employee 类中的 mailCheck() 方法。

这里,在编译时,编译器使用 Employee 中的 mailCheck() 来验证这个语句。 然而,在运行时,JVM 调用 Salary 类中的 mailCheck()。

这种行为称为虚拟方法调用,这些方法称为虚拟方法。 无论在编译时源代码中使用的引用是什么数据类型,都会在运行时调用被重写的方法。

查看笔记

扫码一下
查看教程更方便