Java 简明教程

Java - Polymorphism

Polymorphism in Java

多态性是指对象具备多种形态的能力。多态性是 Java OOPs concept 的一项重要特性,并且它允许我们使用任何方法(接口)的单一名称来执行多个操作。任何能够通过多个 IS-A 测试的 Java 对象都被视为多态的。在 Java 中,所有 Java objects 都是多态的,因为任何对象都可以通过自身类型和 Object 类的 IS-A 测试。

Use of Polymorphism in Java

面向对象编程中多态性最常见的用法是当父类引用用于引用子类对象时。

值得注意的是访问对象的唯一可能方式是通过 reference variable。引用变量只有一种类型。声明后,引用变量的类型不能修改。

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

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

Java Polymorphism Example

我们来看一个示例。

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

现在,Deer 类被认为是多态的,因为它具有多重继承。以下是上述示例的 true 内容 −

  1. A Deer IS-A Animal

  2. A Deer IS-A Vegetarian

  3. A Deer IS-A Deer

  4. A Deer IS-A Object

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

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

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

Java Polymorphism Implementation

在此示例中,我们通过创建 Deer 对象并将相同的对象分配给超类或已实现接口的引用来展示上述概念。

interface Vegetarian{}
class Animal{}
public class Deer extends Animal implements Vegetarian{
	public static void main(String[] args) {
		Deer d = new Deer();
		Animal a = d;
		Vegetarian v = d;
		Object o = d;

		System.out.println(d instanceof Deer);
		System.out.println(a instanceof Deer);
		System.out.println(v instanceof Deer);
		System.out.println(o instanceof Deer);
	}
}

Output

true
true
true
true

Types of Java Polymorphism

Java 中有两种多态性:

Compile Time Polymorphism in Java

编译时多态性也称为静态多态性,并且它是由 method overloading 实现的。

此示例具有多个具有相同名称的方法来实现 Java 中的编译时多态性的概念。

// Java Example: Compile Time Polymorphism
public class Main {
  // method to add two integers
  public int addition(int x, int y) {
    return x + y;
  }

  // method to add three integers
  public int addition(int x, int y, int z) {
    return x + y + z;
  }

  // method to add two doubles
  public double addition(double x, double y) {
    return x + y;
  }

  // Main method
  public static void main(String[] args) {
    // Creating an object of the Main method
    Main number = new Main();

    // calling the overloaded methods
    int res1 = number.addition(444, 555);
    System.out.println("Addition of two integers: " + res1);

    int res2 = number.addition(333, 444, 555);
    System.out.println("Addition of three integers: " + res2);

    double res3 = number.addition(10.15, 20.22);
    System.out.println("Addition of two doubles: " + res3);
  }
}

Output

Addition of two integers: 999
Addition of three integers: 1332
Addition of two doubles: 30.369999999999997

Run Time Polymorphism in Java

运行时多态性也称为动态方法分派,并且它是由 method overriding 实现的。

// Java Example: Run Time Polymorphism
class Vehicle {
  public void displayInfo() {
    System.out.println("Some vehicles are there.");
  }
}

class Car extends Vehicle {
  // Method overriding
  @Override
  public void displayInfo() {
    System.out.println("I have a Car.");
  }
}

class Bike extends Vehicle {
  // Method overriding
  @Override
  public void displayInfo() {
    System.out.println("I have a Bike.");
  }
}

public class Main {
  public static void main(String[] args) {
    Vehicle v1 = new Car(); // Upcasting
    Vehicle v2 = new Bike(); // Upcasting

    // Calling the overridden displayInfo() method of Car class
    v1.displayInfo();

    // Calling the overridden displayInfo() method of Bike class
    v2.displayInfo();
  }
}

Output

I have a Car.
I have a Bike.

Virtual Method and Run Time Polymorphism in Java

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

我们已经讨论了方法覆盖,子类可以在父类中覆盖方法。覆盖方法实质上隐藏在父类中,并且不会被调用,除非子类在覆盖方法中使用 super 关键字。

Example: Implementation of Run Time Polymorphism with Virtual Methods

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

现在,假设我们以如下方式扩展 Employee 类 -

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

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

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

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

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

Output

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()。

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