Java 简明教程

Java - Polymorphism

Polymorphism in Java

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

Polymorphism is the ability of an object to take on many forms. Polymorphism is an important feature of Java OOPs concept and it allows us to perform multiple operations by using the single name of any method (interface). Any Java object that can pass more than one IS-A test is considered to be polymorphic. In Java, all Java objects are polymorphic since any object will pass the IS-A test for its own type and for the class Object.

Use of Polymorphism in Java

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

The most common use of polymorphism in OOP occurs when a parent class reference is used to refer to a child class object.

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

It is important to know that the only possible way to access an object is through a reference variable. A reference variable can be of only one type. Once declared, the type of a reference variable cannot be changed.

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

The reference variable can be reassigned to other objects provided that it is not declared final. The type of the reference variable would determine the methods that it can invoke on the object.

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

A reference variable can refer to any object of its declared type or any subtype of its declared type. A reference variable can be declared as a class or interface type.

Java Polymorphism Example

我们来看一个示例。

Let us look at an example.

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

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

Now, the Deer class is considered to be polymorphic since this has multiple inheritance. Following are true for the above examples −

  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 对象引用时,以下声明是合法的 −

When we apply the reference variable facts to a Deer object reference, the following declarations are legal −

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

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

All the reference variables d, a, v, o refer to the same Deer object in the heap.

Java Polymorphism Implementation

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

In this example, we’re showcasing the above concept by creating the object of a Deer and assigning the same to the references of superclasses or implemented interface.

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 中有两种多态性:

There are two types of polymorphism in Java:

Compile Time Polymorphism in Java

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

Compile-time polymorphism is also known as static polymorphism and it is implemented by method overloading.

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

This example has multiple methods having the same name to achieve the concept of compile-time polymorphism in 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 实现的。

Run time polymorphism is also known as dynamic method dispatch and it is implemented by the 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 中重写方法的行为如何允许你在设计类时利用多态性。

In this section, I will show you how the behavior of overridden methods in Java allows you to take advantage of polymorphism when designing your classes.

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

We already have discussed method overriding, where a child class can override a method in its parent. An overridden method is essentially hidden in the parent class, and is not invoked unless the child class uses the super keyword within the overriding method.

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 类 -

Now suppose we extend Employee class as follows −

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

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

Now, you study the following program carefully and try to determine its output −

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

Here, we instantiate two Salary objects. One using a Salary reference s, and the other using an Employee reference e.

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

While invoking s.mailCheck(), the compiler sees mailCheck() in the Salary class at compile time, and the JVM invokes mailCheck() in the Salary class at run time.

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

mailCheck() on e is quite different because e is an Employee reference. When the compiler sees e.mailCheck(), the compiler sees the mailCheck() method in the Employee class.

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

Here, at compile time, the compiler used mailCheck() in Employee to validate this statement. At run time, however, the JVM invokes mailCheck() in the Salary class.

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

This behavior is referred to as virtual method invocation, and these methods are referred to as virtual methods. An overridden method is invoked at run time, no matter what data type the reference is that was used in the source code at compile time.