
6.7 对象的多态性
多态性在面向对象中是一个最重要的概念,在Java中面向对象主要有以下两种主要体现:
(1)方法的重载与覆写。
(2)对象的多态性。
对于方法的重载与覆写,本书之前已经为读者作过详细介绍,下面将重点介绍对象的多态性。
对象的多态性主要分为以下两种类型:
(1)向上转型:子类对象→父类对象。
(2)向下转型:父类对象→子类对象。
对于向上转型,程序会自动完成,而对于向下转型时,必须明确的指明要转型的子类类型,如格式6-7所示。
【格式6-7 对象转型】

下面介绍如何进行对象的向上转型操作,具体代码如下。
【例6.30】对象的向上转型

程序执行结果:

上面的程序就是一个对象向上转型关系,从程序的运行结果中,可以发现此时虽然是使用父类对象调用了fun1()方法,但实际上调用的方法是被子类所覆写过的方法,也就是说,如果对象发生了向上转型关系,所调用的方法肯定是被子类覆写过的方法。但是在此一定要注意,此时a对象是无法调用B类中的fun3()方法的,因为此方法只在子类定义,而没有在父类中定义,如果要想调用子类定义的其他的方法,肯定要使用子类实例,所以此时可以将对象向下转型。
【例6.31】对象的向下转型

程序执行结果:

从上面的程序中可以发现,如果想调用子类自己的方法,则只能用子类声明对象,另外,在子类中调用了父类中继承而来的fun2()方法,fun2()方法要调用fun1()方法,由于此时fun1()方法已经被子类所覆写,所以,此时调用的方法是被子类覆写过的方法。
注意
对象向下转型的要求。
在上面的程序中已经介绍了对象的向上和向下转型的基本概念,但是在进行对象的向下转型之前,必须首先发生对象向上转型,否则将出现对象转换异常,如下面代码所示。
实例:错误的转型

程序执行结果:

由上面的程序可以发现,此时的A类对象是由A类本身进行实例化的,然后将A类的实例化对象强制转换为子类对象,这样写在语法上是没有任何错误的,但是在运行时出现了异常,这是为什么呢?为什么父类不可以向子类转换了呢?其实这点并不难理解,读者可以想一下在现实生活中的例子。假如你今天刚买完一些生活用品,回家的时候在路上碰见一个孩子,这个孩子忽然对你说:“我是你的儿子,你把你买的东西给我吧!”,这个时候你肯定不会把你的东西给这个孩子,因为你不确定他跟你是否有关系。那么在上面的程序中也是同样的道理,父类用其本身类实例化自己的对象,但它并不知道谁是自己的子类,那肯定在转换的时候会出现错误,那么这个错误该如何纠正呢?只需要将两个对象建立好关系即可,在声明父类对象时先发生向上转型关系:“A a=new B();”这个时候相当于是由子类去实例化父类对象,也就是说这个时候父类知道有这么一个子类,也就相当于父亲知道了自己有这么一个孩子,所以下面再进行转换的时候就不会再有问题。
了解了对象多态性之后,那么这个概念到底有哪些用处呢?下面要求设计一个方法,要求此方法可以接收A类的任意子类对象,并调用方法,此时,如果不使用对象多态性,则肯定会使用以下形式的代码。
【例6.32】不使用对象多态性实现功能

程序执行结果:

上面的程序虽然实现了基本的要求,但是读者应该可以发现:如果真要按照以上的方式完成程序,则当产生了一个A类的子类时,fun()方法就要重载一次,这样则每一次扩充子类都必须修改类本身,那么如果现在使用对象多态性完成呢?具体代码如下:
【例6.33】使用对象多态性实现此功能

程序执行结果:

上面的程序由于在fun()方法中使用了对象的多态性,所以可以接收任何的子类对象,这样不管子类如何增加,则fun()方法都不用做任何的改变,因为一旦发生对象的向上转型关系之后,调用的方法肯定是被子类覆写过的方法。