JAVA从入门到放弃(6)多态

J

上一篇笔记中介绍了复用类,而这一篇笔记中想介绍一下多态。

在讨论多态之前,我们需要重新讨论一下向上转型这个问题。在之前的向上转型当中存在一个问题就是:我们为什么要故意忘记变量的类型。JAVA明明是一种强类型语言,但是在向上转型的过程中我们却故意忘记变量的类型。首先先来看一段代码

在上面的代码中我们如果让tune方法直接接受WindC型的引用可能会更加直观,但是为什么还是要使用这种向上转型的方法?而且在这段代码中我们明明将WindC类向上转型成了InstrumentC,所以在调用时应该显示Instrument.play()而不是最后结果的Wind.play(),但是为什么最后输出是Wind.play()?

首先来解释一下第一个比较奇怪的问题,如果直接接受WindC类,这样的话就需要在所有的InstrumentC的所有导出类中声明tune()方法,也就是下面这样

必须对tune进行重载,细化到支持每一个导出类,这样会让代码很繁琐,而且还会有一个很头疼的问题就是:如果你不重载编译器也不会报错0.0

(奶奶的说好的智能呢)(醒醒吧哪有那么多智能,有个代码提示和高亮就不错了)

所以我们要使用上面的那种写法,接下来来回答第二个问题,第二个问题也就是这一篇笔记的重点:多态。

在真是介绍多态之前,还需要对上面程序的几个疑点进行一下解释。例如:为什么我们在WindC类发生向InstrumentC类的向上转型后,为什么执行play的时候执行的不是InstrumentC的play方法而是WindC的play方法,也就是前面提到的那个问题。这中间究竟发生了什么。还得一点一点慢慢说

首先来说一下方法调用的绑定问题:将一个方法调用同一个方法主题关联起来被称为绑定。绑定分为前期绑定和后期绑定,常说的前期绑定就是之前我们在C语言中经常遇到的绑定方法。而在JAVA中常见的就是后期绑定,所谓的后期绑定就是在运行时根据对象的类型进行绑定。这就比较智能了。后期绑定也称为动态绑定或者运行时绑定,在JAVA中除了static和final方法剩下的都使用的是后期绑定。再回到前面我们说的为什么要使用final,在这里就会发现第二个作用就是使用final可以有效的关闭动态绑定。

既然我们了解了后期绑定,那么对于前面提到的那个问题就自然而然可以解决了。这里还要说一下的是继承,向上转型,多态之间的关系。继承创造了向上转型,向上转型又实现了多态。因此在后期我们只需要编写和基类打交道的代码就可以了,而且这些代码可以对这个基类的所有导出类都正确运行,这就是多态的好处。

既然是向上转型实现了多态,那么就需要看一下另一种向上转型方法。假设Shape是基类,Circle是导出类

这样也可以实现一种向上转型,这里通过继承Circle也是一种Shape。但只是这里如果调用s.draw()可能就会想当然的认为这里调用的是Shape类的draw方法,但是因为JAVA中存在后期绑定,所以正确的姿势是调用了Circle的draw方法

正是由于这种机制的存在才会使得我们可以根据自己的需要对系统添加任意多的新类,而不需要改变基类方法,这就是我们这里要提到的多态。多态可以保证一个程序是可拓展的。JAVA编程思想中说:多态是一想让程序员“将改变的食物与未变的事物分离开来”的重要技术。但是这里有两个值得注意的地方:一个是覆盖私有方法会造成的问题,另一个是域和静态方法的问题。

自然私有方法前面已经说了,private默认都是final,也就是对外部不可见,所以当尝试重载一个private方法时实际上是不会重载成功的。这个相信很多人还是都明白的,另一点就是并不是所有的事物都可以多态的发生,就像在上面说到的域和静态方法的问题,说清楚这个问题还是要看一段代码

这里会出现一个比较有趣的现象,sub.field是1,sup.geiField得到的结果还是1。这是因为域访问操作都是编译器进行解析的因此不存在多态,所以实际上在sub包含两个称为Field的域一个是它自己的一个是他的父类Super的,如果想要得到父类的Field,就要使用super显式声明。这是域的问题,如果一个方法是静态的自然也就不具有多态性了。

既然要介绍多态,那怎么能忘记构造器的多态性,但是需要注意的是构造器实际上是static类型,只是static关键字被省略了,但是前面说了static方法是不具有多态的。但是这里为什么又要强调构造器的多态呢,其实构造器本身不具有多态,在这里要讨论的是构造器在多态这种复杂的层次结构中是怎么运作的。

首先根据前面的构造器调用的基本规则,导出类构造器是需要调用基类构造器的,在这里也是一样,导出类的构造器也是需要调用基类构造器的。接下来会按照声明的顺序调用成员的初始化方法,然后再调用导出类构造器。千万不要忘记在导出类中调用基类构造器,不然会大麻烦的。当然,在构造器中还有一个大麻烦就是构造器内部的多态行为。我们都知道多态使用的是动态绑定,如果我们在构造器中使用多态,可能会造成当我们调用基类构造器时导出类构造器还没有被调用,这个时候却是用多态调用了没有被初始化的导出类。于是乎0.0,GG。可能这样不太好理解,来看一下代码

正如这一段代码以及运行结果那样,因为Glyph.draw设计成了要被覆盖,而这种覆盖是由RoundGlyph.draw实现的,但是这时RoundGlyph还没有被初始化,因此就出了这种执行结果。所以结合这段代码我们或许可以看出JAVA实际上是在任何事物发生之前都将其对应的存储空间初始化全零。(还有这种操作?)不过这样至少保证了数据被初始化了,而不再是乱码了。接下来看一下另一个JAVA SE5的新功能就是协变返回类型,首先还是先看一下协变返回类型的代码

这个就是JAVA中的协变返回类型,其实还是挺好理解的,在JAVA SE5版本之前process函数只能返回Grain而不是Wheat,然而现在可以要自行车了

About the author

NOBUG.IN

1 comment

By NOBUG.IN

Your sidebar area is currently empty. Hurry up and add some widgets.