8.3 多态

  抽象、封装和继承的学习已告一段落,接下来要学习面向对象设计与开发中比较难理解的一个概念——多态。直接描述多态的概念,对于一个没有体会过多态好处的人来说,是一件困难的事情,所以本节仍然以“租车系统”为例,给大家介绍多态。

8.3.1 向上转型

  阅读“租车系统”的需求,发现程序中需要新建一个驾驶员(租车者)类,这个类有一个姓名的属性,还有两个获取车辆信息的方法,具体代码如下所示。

  1. package com.bd.zuche;
  2. //驾驶员(租车者)类
  3. public class Driver
  4. {
  5. String name = "驾驶员"; //驾驶员姓名
  6. //构造方法,指定驾驶员名
  7. public Driver(String name)
  8. {
  9. this.name = name;
  10. }
  11. //获取驾驶员名
  12. public String getName()
  13. {
  14. return name;
  15. }
  16. //驾驶员获取轿车车辆信息,输入参数为轿车对象
  17. public void callShow(Car car)
  18. {
  19. car.show();
  20. }
  21. //驾驶员获取卡车车辆信息,输入参数为卡车对象
  22. public void callShow(Truck truck)
  23. {
  24. truck.show();
  25. }
  26. }

  使用下面的代码进行测试,运行结果如图8.15所示。

  1. import com.bd.zuche.*;
  2. class TestZuChe4
  3. {
  4. public static void main(String[] args)
  5. {
  6. Car car = new Car("战神","长城"); //初始化轿车对象car
  7. Truck truck = new Truck("大力士二代","10吨"); //初始化卡车对象truck
  8. Driver d1 = new Driver("柳海龙"); //创建并初始化驾驶员对象
  9. d1.callShow(car); //调用驾驶员对象相应的方法
  10. d1.callShow(truck); //调用驾驶员对象相应的方法
  11. }
  12. }
8.3 多态 - 图1
图8.15 “租车系统”运行结果

  在写Driver类的过程中,驾驶员获取车辆信息的功能用了两个重载方法,如果要获取轿车信息,则输入的是轿车对象,方法体内调用轿车对象的方法;如果要获取卡车信息,则输入的是卡车对象,方法体内调用卡车对象的方法。如果需要从Vehicle类继承出10种车辆类型,则在Driver类需要中写10个方法。这样的做法,看起来有些傻!

  接下来用多态的方式解决这个问题。

  首先要在Vehicle类中增加一个show()方法,方法体为空,这样Car类和Truck类中的show()方法实际是重写了Vehicle类中的show()方法,代码如下。

  1. public class Vehicle
  2. {
  3. //省略其他代码
  4. //显示车辆信息
  5. public void show()
  6. {
  7. }
  8. }

  接下来修改Driver类,将原来两个callShow()方法合并成一个方法,输入参数不再是具体的车辆类型,而是这些车辆类型的父类Vehicle,在方法体内调用父类的show()方法。

  1. package com.bd.zuche;
  2. //驾驶员(租车者)类
  3. public class Driver
  4. {
  5. String name = "驾驶员"; //驾驶员姓名
  6. //构造方法,指定驾驶员名
  7. public Driver(String name)
  8. {
  9. this.name = name;
  10. }
  11. //获取驾驶员名
  12. public String getName()
  13. {
  14. return name;
  15. }
  16. //驾驶员获取车辆信息,输入参数为车对象
  17. public void callShow(Vehicle v)
  18. {
  19. v.show();
  20. }
  21. }

  运行以下测试代码,程序正常运行。

  1. import com.bd.zuche.*;
  2. class TestZuChe5
  3. {
  4. public static void main(String[] args)
  5. {
  6. Car car = new Car("战神","长城"); //初始化轿车对象car
  7. Truck truck = new Truck("大力士二代","10吨"); //初始化卡车对象truck
  8. Driver d1 = new Driver("柳海龙"); //创建并初始化驾驶员对象
  9. d1.callShow(car); //调用驾驶员对象的相应方法
  10. d1.callShow(truck); //调用驾驶员对象的相应方法
  11. }
  12. }

  总结如下。

  (1)在父类Vehicle类中有show()方法。

  (2)在子类Car类和Truck类中重写了show()方法,实现了不同的功能。

  (3)在Driver类中,callShow(Vehicle v)方法的形参是一个父类对象。

  (4)在测试类的代码中,d1.callShow(car);和d1.callShow(truck);这两行语句调用callShow (Vehicle v)方法时,实际传入的是子类对象,最终执行的是子类对象重写的show()方法,而不是父类对象的show()方法。

  这就是多态的最重要的一种形式:向上转型,即父类的引用指向子类对象,也就是上面例子中Vehicle类的引用,指向了car和truck这两个对象。

  向上转型的好处,不仅是在Driver类中不需要针对Vehicle类的多个子类写多个方法,减少了代码编写量,而且增加了程序的扩展性。即在现有程序架构的基础上,可以再设计开发出若干个Vehicle类的子类(重写show()方法),这样在不用更改Driver类的情况下,就可以通过在测试类中创建新的子类,调用Driver类的callShow(Vehicle v)方法,实现对新的子类功能扩展。

  向上转型虽然可以减少代码量,增加程序的可扩展性,但同时也有自身的问题。例如,在Driver类的callShow(Vehicle v)方法中,只能调用Vehicle类的方法,不能调用Vehicle类子类特有的方法(例如Car类中getBrand()方法),这就是向上转型的局限性。

8.3.2 向下转型

  向上转型是父类的引用指向子类对象,也就是Vehicle类的引用,指向了car和truck这两个对象。那么同理,向下转型就是子类引用指向父类对象,也就是说可能明明需要一个 car类引用,却提供了一个Vehicle类的对象。显然,这样的做法是不安全的。

  下面来看一个例子。

  1. import com.bd.zuche.*;
  2. class TestZuChe6
  3. {
  4. public static void main(String[] args)
  5. {
  6. Vehicle v = new Car("战神","长城"); //声明父类对象,实例化出子类对象
  7. v.show(); //实际调用子类重写父类的show()方法
  8. //System.out.println(v.getBrand());; //编译错误,无法调用子类特有的方法
  9. Car car = (Car)v; //将对象v强制类型转换成Car类对象
  10. System.out.println(car.getBrand()); //调用Car类特有方法getBrand()
  11. Truck truck = (Truck)v; //将对象v强制类型转换成Truck类对象
  12. System.out.println(truck.getLoad()); //调用Truck类特有方法getLoad()
  13. }
  14. }

  程序运行结果如图8.16所示。

8.3 多态 - 图2
图8.16 向下转型

  从运行结果可以看出,对象v是可以强制类型转换成Car类型的,因为它本身实例化的时候就是Car类型,所以可以进行强制类型转换,并且转换完之后可以调用Car类特有的方法getBrand()。但是对象v是不可以强制类型转换成Truck类型的,对象v实例化的时候是Car类型,把Car类型转换成Truck类型,会抛出异常。

  程序员编程的过程中,在进行对象的强制类型转换时,不知道具体运行时是否会抛出异常,这种情况是不能接受的。Java提供了instanceof运算符(不是方法),可以进行类型判断,避免抛出异常。instanceof运算符的语法形式如下。

  1. 对象instanceof

  该运算符判断一个对象是否属于一个类,返回值为true或false。

  看下面的例子。

  1. import com.bd.zuche.*;
  2. class TestZuChe7
  3. {
  4. public static void main(String[] args)
  5. {
  6. //Vehicle v = new Car("战神","长城"); //声明父类对象,实例化出子类对象
  7. Vehicle v = new Truck("大力士二代","10吨");
  8. v.show();
  9. if( v instanceof Car ) //对象v属于Car类型
  10. {
  11. Car car = (Car)v; //将对象v强制类型转换成Car类对象
  12. System.out.println(car.getBrand()); //调用Car类的特有方法getBrand()
  13. }else{ //对象v不属于Car类型
  14. Truck truck = (Truck)v; //将对象v强制类型转换成Truck类对象
  15. System.out.println(truck.getLoad()); //调用Truck类的特有方法getLoad()
  16. }
  17. }
  18. }

  通过instanceof判断对象v属于哪个类,再进行强制类型转换,减少强制类型转换抛出异常的可能。程序运行结果如图8.17所示。

8.3 多态 - 图3
图8.17 instanceof运算符使用

  此外,不难发现多态的两种表现形式就是重载和重写。例如,通过Driver类中重载的callShow()方法调用不同对象的show()方法,通过重写后的show()方法获取不同类型的车辆信息。