多态性指的是一个对象能够呈现多种形态的能力。它是 Java 面向对象编程概念的重要特征之一,允许我们使用单一的方法名(接口)执行多种操作。如果一个 Java 对象能通过多次 IS-A 测试(即它可以被认为是多个类的实例),那么该对象就被认为是多态的。在 Java 中,所有的 Java 对象都是多态的,因为任何一个对象都能通过其自身类型和 Object
类型的 IS-A 测试。
Java 中多态性的使用
在面向对象编程中最常见的多态形式是当父类引用被用来引用子类对象时发生的。了解引用变量是访问对象的唯一方式是很重要的。引用变量只能是一个类型,一旦声明后,引用变量的类型就不能改变。然而,只要不是最终(final)声明的,引用变量可以重新赋值为其他对象。引用变量的类型决定了它可以调用对象上的哪些方法。
一个引用变量可以指向其声明类型或其声明类型的任何子类型的任何对象。引用变量可以声明为类类型或接口类型。
Java 多态性示例
让我们来看一个例子:
public interface Vegetarian {}
class Animal {}
public class Deer extends Animal implements Vegetarian {}
现在,Deer
类被认为是多态的,因为它具有多重继承(这里的多重继承指的是它继承了 Animal
并且实现了 Vegetarian
接口)。以下对于上面的例子是正确的:
当我们把引用变量的事实应用到 Deer
对象上时,以下声明是合法的:
Deer d = new Deer();
Animal a = d;
Vegetarian v = d;
Object o = d;
所有的引用变量 d
, a
, v
, o
指向堆内存中的同一个 Deer
对象。
Java 多态性的实现
在这个例子中,我们通过创建 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);
}
}
输出:
true
true
true
true
Java 多态性的类型
Java 中有两种多态性:
-
编译时多态性(Compile Time Polymorphism)
-
运行时多态性(Run Time Polymorphism)
编译时多态性
编译时多态性也称为静态多态性,它是通过方法重载(Method Overloading)来实现的。
示例:编译时多态性
此示例中有多个具有相同名称的方法来实现编译时多态性的概念:
public class Main {
public int addition(int x, int y) {
return x + y;
}
public int addition(int x, int y, int z) {
return x + y + z;
}
public double addition(double x, double y) {
return x + y;
}
public static void main(String[] args) {
Main number = new Main();
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);
}
}
输出:
Addition of two integers: 999
Addition of three integers: 1332
Addition of two doubles: 30.37
运行时多态性
运行时多态性也称为动态方法调度(Dynamic Method Dispatch),它是通过方法覆盖(Method Overriding)来实现的。
示例:运行时多态性
class Vehicle {
public void displayInfo() {
System.out.println("Some vehicles are there.");
}
}
class Car extends Vehicle {
@Override
public void displayInfo() {
System.out.println("I have a Car.");
}
}
class Bike extends Vehicle {
@Override
public void displayInfo() {
System.out.println("I have a Bike.");
}
}
public class Main {
public static void main(String[] args) {
Vehicle v1 = new Car();
Vehicle v2 = new Bike();
v1.displayInfo();
v2.displayInfo();
}
}
输出:
I have a Car.
I have a Bike.
Java 中的虚方法与运行时多态性
在这一部分,我们将展示 Java 中如何通过方法重写(method overriding)来利用多态性设计类。
我们已经讨论过方法重写,其中子类可以重写其父类中的方法。一个被重写的方法实际上在父类中被隐藏了,除非子类在重写的方法中使用 super
关键字来显式调用它。
示例:带有虚方法的运行时多态性实现
首先,我们有 Employee
类:
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
类:
public class Salary extends Employee {
private double 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;
}
}
现在,仔细研究下面的程序并尝试确定其输出:
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()
时,编译器在编译时期看到 mailCheck()
在 Salary
类中,而在运行时期,JVM 调用的是 Salary
类中的 mailCheck()
方法。
而对于 e.mailCheck()
,尽管 e
是一个 Employee
类型的引用,但在编译期编译器看到的是 Employee
类中的 mailCheck()
方法。然而,在运行期,JVM 实际上调用的是 Salary
类中的 mailCheck()
方法。
这种行为被称为虚拟方法调用,这样的方法被称为虚方法。无论在源代码中使用的引用的数据类型是什么,被重写的方法都会在运行时被调用。