Java 多态


多态是同一个行为具有多个不同表现形式或形态的能力。

多态性是对象多种表现形式的体现。

比如我们说"宠物"这个对象,它就有很多不同的表达或实现,比如有小猫、小狗、蜥蜴等等。那么我到宠物店说"请给我一只宠物",服务员给我小猫、小狗或者蜥蜴都可以,我们就说"宠物"这个对象就具备多态性。

接下来让我们通过实例来了解Java的多态。

例子

  1. public interface Vegetarian{}
  2. public class Animal{}
  3. public class Deer extends Animal implements Vegetarian{}

因为Deer类具有多重继承,所以它具有多态性。以上实例解析如下:

  • 一个 Deer IS-A(是一个) Animal
  • 一个 Deer IS-A(是一个) Vegetarian
  • 一个 Deer IS-A(是一个) Deer
  • 一个 Deer IS-A(是一个)Object

在Java中,所有的对象都具有多态性,因为任何对象都能通过IS-A测试的类型和Object类。

访问一个对象的唯一方法就是通过引用型变量。

引用型变量只能有一种类型,一旦被声明,引用型变量的类型就不能被改变了。

引用型变量不仅能够被重置为其他对象,前提是这些对象没有被声明为final。还可以引用和它类型相同的或者相兼容的对象。它可以声明为类类型或者接口类型。

当我们将引用型变量应用于Deer对象的引用时,下面的声明是合法的:

  1. Deer d = new Deer();
  2. Animal a = d;
  3. Vegetarian v = d;
  4. Object o = d;

所有的引用型变量d,a,v,o都指向堆中相同的Deer对象。

虚方法

我们将介绍在Java中,当设计类时,被重载的方法的行为怎样影响多态性。

我们已经讨论了方法的重载,也就是子类能够重载父类的方法。

当子类对象调用重载的方法时,调用的是子类的方法,而不是父类中被重载的方法。

要想调用父类中被重载的方法,则必须使用关键字super。

  1. /* 文件名 : Employee.java */
  2. public class Employee
  3. {
  4. private String name;
  5. private String address;
  6. private int number;
  7. public Employee(String name, String address, int number)
  8. {
  9. System.out.println("Constructing an Employee");
  10. this.name = name;
  11. this.address = address;
  12. this.number = number;
  13. }
  14. public void mailCheck()
  15. {
  16. System.out.println("Mailing a check to " + this.name
  17. + " " + this.address);
  18. }
  19. public String toString()
  20. {
  21. return name + " " + address + " " + number;
  22. }
  23. public String getName()
  24. {
  25. return name;
  26. }
  27. public String getAddress()
  28. {
  29. return address;
  30. }
  31. public void setAddress(String newAddress)
  32. {
  33. address = newAddress;
  34. }
  35. public int getNumber()
  36. {
  37. return number;
  38. }
  39. }

假设下面的类继承Employee类:

  1. /* 文件名 : Salary.java */
  2. public class Salary extends Employee
  3. {
  4. private double salary; //Annual salary
  5. public Salary(String name, String address, int number, double
  6. salary)
  7. {
  8. super(name, address, number);
  9. setSalary(salary);
  10. }
  11. public void mailCheck()
  12. {
  13. System.out.println("Within mailCheck of Salary class ");
  14. System.out.println("Mailing check to " + getName()
  15. + " with salary " + salary);
  16. }
  17. public double getSalary()
  18. {
  19. return salary;
  20. }
  21. public void setSalary(double newSalary)
  22. {
  23. if(newSalary >= 0.0)
  24. {
  25. salary = newSalary;
  26. }
  27. }
  28. public double computePay()
  29. {
  30. System.out.println("Computing salary pay for " + getName());
  31. return salary/52;
  32. }
  33. }

现在我们仔细阅读下面的代码,尝试给出它的输出结果:

  1. /* 文件名 : VirtualDemo.java */
  2. public class VirtualDemo
  3. {
  4. public static void main(String [] args)
  5. {
  6. Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
  7. Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
  8. System.out.println("Call mailCheck using Salary reference --");
  9. s.mailCheck();
  10. System.out.println("\n Call mailCheck using Employee reference--");
  11. e.mailCheck();
  12. }
  13. }

以上实例编译运行结果如下:

  1. Constructing an Employee
  2. Constructing an Employee
  3. Call mailCheck using Salary reference --
  4. Within mailCheck of Salary class
  5. Mailing check to Mohd Mohtashim with salary 3600.0
  6.  
  7. Call mailCheck using Employee reference--
  8. Within mailCheck of Salary class
  9. Mailing check to John Adams with salary 2400.0

例子中,我们实例化了两个Salary对象。一个使用Salary引用s,另一个使用Employee引用。

编译时,编译器检查到mailCheck()方法在Salary类中的声明。

在调用s.mailCheck()时,Java虚拟机(JVM)调用Salary类的mailCheck()方法。

因为e是Employee的引用,所以调用e的mailCheck()方法则有完全不同的结果。

当编译器检查e.mailCheck()方法时,编译器检查到Employee类中的mailCheck()方法。

在编译的时候,编译器使用Employee类中的mailCheck()方法验证该语句, 但是在运行的时候,Java虚拟机(JVM)调用的是Salary类中的mailCheck()方法。

该行为被称为虚拟方法调用,该方法被称为虚拟方法。

Java中所有的方法都能以这种方式表现,借此,重写的方法能在运行时调用,不管编译的时候源代码中引用变量是什么数据类型。