Java 中的多态

多态是对象呈现多种形式的能力。多态性是Java OOPs概念的一个重要特性,它允许我们使用任何方法(接口)的单一名称来执行多个操作。任何可以通过多个 IS-A 测试的 Java 对象都被认为是多态的。在 Java 中,所有 Java 对象 都是多态的,因为任何对象都会通过其自身类型和类 Object 的 IS-A 测试。

Java 中多态性的使用

OOP 中多态性最常见的用法发生在使用父类引用来引用子类对象时。

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

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

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

Java 多态性示例

让我们看一个示例。

public interface Vegetarian{}//素食
public class Animal{}//动物
public class Deer extends Animal implements Vegetarian{} //鹿

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

  • 鹿是动物
  • 鹿是素食者
  • 鹿 IS-A 鹿
  • 鹿 IS-A 对象

当我们将引用变量事实应用于 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 中的多态性有两种:

  1. 编译时多态性
  2. 运行时多态性

Java 中的编译时多态

编译时多态也称为静态多态,它是通过 方法重载。

示例:编译时多态性

该示例使用多个同名方法来实现 Java 中编译时多态性的概念。

// Java示例:编译时多态
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方法的对象
    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.369999999999997 

Java 中的运行时多态

运行时多态也称为动态方法分派,它的实现通过方法重写。

示例:运行时多态性

// Java 示例:运行时多态
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(); // 向上转型

    //调用Car类重写的displayInfo()方法
    v1.displayInfo();

    //调用Bike类重写的displayInfo()方法
    v2.displayInfo();
  }
} 

输出

I have a Car.
I have a Bike. 

Java 中的虚拟方法和运行时多态性

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

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

示例:使用虚拟方法实现运行时多态性

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

输出

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 对象。一个使用薪资参考 s,另一个使用员工参考 e

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

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

这里,在编译时,编译器在员工验证此声明。然而,在运行时,JVM 会调用 Salary 类中的 mailCheck()。

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