8.3 多态
抽象、封装和继承的学习已告一段落,接下来要学习面向对象设计与开发中比较难理解的一个概念——多态。直接描述多态的概念,对于一个没有体会过多态好处的人来说,是一件困难的事情,所以本节仍然以“租车系统”为例,给大家介绍多态。
8.3.1 向上转型
阅读“租车系统”的需求,发现程序中需要新建一个驾驶员(租车者)类,这个类有一个姓名的属性,还有两个获取车辆信息的方法,具体代码如下所示。
package com.bd.zuche;
//驾驶员(租车者)类
public class Driver
{
String name = "驾驶员"; //驾驶员姓名
//构造方法,指定驾驶员名
public Driver(String name)
{
this.name = name;
}
//获取驾驶员名
public String getName()
{
return name;
}
//驾驶员获取轿车车辆信息,输入参数为轿车对象
public void callShow(Car car)
{
car.show();
}
//驾驶员获取卡车车辆信息,输入参数为卡车对象
public void callShow(Truck truck)
{
truck.show();
}
}
使用下面的代码进行测试,运行结果如图8.15所示。
import com.bd.zuche.*;
class TestZuChe4
{
public static void main(String[] args)
{
Car car = new Car("战神","长城"); //初始化轿车对象car
Truck truck = new Truck("大力士二代","10吨"); //初始化卡车对象truck
Driver d1 = new Driver("柳海龙"); //创建并初始化驾驶员对象
d1.callShow(car); //调用驾驶员对象相应的方法
d1.callShow(truck); //调用驾驶员对象相应的方法
}
}
在写Driver类的过程中,驾驶员获取车辆信息的功能用了两个重载方法,如果要获取轿车信息,则输入的是轿车对象,方法体内调用轿车对象的方法;如果要获取卡车信息,则输入的是卡车对象,方法体内调用卡车对象的方法。如果需要从Vehicle类继承出10种车辆类型,则在Driver类需要中写10个方法。这样的做法,看起来有些傻!
接下来用多态的方式解决这个问题。
首先要在Vehicle类中增加一个show()方法,方法体为空,这样Car类和Truck类中的show()方法实际是重写了Vehicle类中的show()方法,代码如下。
public class Vehicle
{
//省略其他代码
//显示车辆信息
public void show()
{
}
}
接下来修改Driver类,将原来两个callShow()方法合并成一个方法,输入参数不再是具体的车辆类型,而是这些车辆类型的父类Vehicle,在方法体内调用父类的show()方法。
package com.bd.zuche;
//驾驶员(租车者)类
public class Driver
{
String name = "驾驶员"; //驾驶员姓名
//构造方法,指定驾驶员名
public Driver(String name)
{
this.name = name;
}
//获取驾驶员名
public String getName()
{
return name;
}
//驾驶员获取车辆信息,输入参数为车对象
public void callShow(Vehicle v)
{
v.show();
}
}
运行以下测试代码,程序正常运行。
import com.bd.zuche.*;
class TestZuChe5
{
public static void main(String[] args)
{
Car car = new Car("战神","长城"); //初始化轿车对象car
Truck truck = new Truck("大力士二代","10吨"); //初始化卡车对象truck
Driver d1 = new Driver("柳海龙"); //创建并初始化驾驶员对象
d1.callShow(car); //调用驾驶员对象的相应方法
d1.callShow(truck); //调用驾驶员对象的相应方法
}
}
总结如下。
(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类的对象。显然,这样的做法是不安全的。
下面来看一个例子。
import com.bd.zuche.*;
class TestZuChe6
{
public static void main(String[] args)
{
Vehicle v = new Car("战神","长城"); //声明父类对象,实例化出子类对象
v.show(); //实际调用子类重写父类的show()方法
//System.out.println(v.getBrand());; //编译错误,无法调用子类特有的方法
Car car = (Car)v; //将对象v强制类型转换成Car类对象
System.out.println(car.getBrand()); //调用Car类特有方法getBrand()
Truck truck = (Truck)v; //将对象v强制类型转换成Truck类对象
System.out.println(truck.getLoad()); //调用Truck类特有方法getLoad()
}
}
程序运行结果如图8.16所示。
从运行结果可以看出,对象v是可以强制类型转换成Car类型的,因为它本身实例化的时候就是Car类型,所以可以进行强制类型转换,并且转换完之后可以调用Car类特有的方法getBrand()。但是对象v是不可以强制类型转换成Truck类型的,对象v实例化的时候是Car类型,把Car类型转换成Truck类型,会抛出异常。
程序员编程的过程中,在进行对象的强制类型转换时,不知道具体运行时是否会抛出异常,这种情况是不能接受的。Java提供了instanceof运算符(不是方法),可以进行类型判断,避免抛出异常。instanceof运算符的语法形式如下。
对象instanceof类
该运算符判断一个对象是否属于一个类,返回值为true或false。
看下面的例子。
import com.bd.zuche.*;
class TestZuChe7
{
public static void main(String[] args)
{
//Vehicle v = new Car("战神","长城"); //声明父类对象,实例化出子类对象
Vehicle v = new Truck("大力士二代","10吨");
v.show();
if( v instanceof Car ) //对象v属于Car类型
{
Car car = (Car)v; //将对象v强制类型转换成Car类对象
System.out.println(car.getBrand()); //调用Car类的特有方法getBrand()
}else{ //对象v不属于Car类型
Truck truck = (Truck)v; //将对象v强制类型转换成Truck类对象
System.out.println(truck.getLoad()); //调用Truck类的特有方法getLoad()
}
}
}
通过instanceof判断对象v属于哪个类,再进行强制类型转换,减少强制类型转换抛出异常的可能。程序运行结果如图8.17所示。
此外,不难发现多态的两种表现形式就是重载和重写。例如,通过Driver类中重载的callShow()方法调用不同对象的show()方法,通过重写后的show()方法获取不同类型的车辆信息。