java基础-多态-多态的理解与使用(2)

一.协变返回类型

概念:在导出类的被覆盖方法可以返回基类型方法的返回类型的某种导出类型。

看一下例子:

导出类WheatMill继承基类Mill,并且覆盖了process()方法。导出类覆盖的方法的返回类型可以是基类该方法返回类型的某种导出类型,也可以与基类相同。输出结果为:

这种特性在jdk1.5之前是不允许的,编译器强制要求导出类覆盖方法的返回类型必须是基类方法的返回类型。

二.纯继承与拓展

在java中,导出类覆盖了基类中所有动态绑定的方法并且自身并没有拓展出另外的任何方法的继承情况称之为纯继承,也称之为纯替代。这种继承的好处在于基类可以接受发送给导出类的任何消息,因为二者有着完全相同的接口。我们只需要从导出类向上转型,永远不需要知道正在处理的对象的确切类型,而所有的这一切,都是通过多态来处理的。所以说纯继承就是一种纯粹的is-a关系。

但是,在实际的业务场景中,有更多的业务逻辑需要导出类除了拥有基类的接口外,还有自己本身拓展出的其他行为。例如,猫是一种动物,它除了拥有着动物共有的基本行为,如吃饭,睡觉外,它还有着它自己所独有的行为,如猫十分好奇,喜欢到处探索,可以抓老鼠,喜欢整理毛发等等。这种它独有的行为并不是所有动物都拥有的。像这种除了覆盖基类所有的动态绑定方法还拥有自己额外的方法的继承关系成为拓展继承。拓展继承更像是一种is-like-a的关系。导出类就像是一个基类-它有着相同的基本接口,但是它还有额外方法实现的其它特性。

拓展继承是一种有用且明智的继承方式,而且在java中也似乎更推荐我们去这样使用。但是它也有缺陷:导出类方法中的拓展部分不能被基类所访问,因此,一旦我们使用了向上转型,就不能调用那些新方法。如果我们不使用向上转型,就不会出现这样的问题。但是通常情况下,我们希望使用向上转型,而且转型后也需要重新查明对象的确切类型,以便能够访问该类型所拓展的方法,这时,该怎么做呢?

向下造型及运行时类型识别(RTTI)

向下造型:把指向导出类对象的基类引用赋给导出类引用,来获取导出类中独有的行为信息。

通过使用向下造型,可以访问到导出类中独有的行为信息。我们知道,向上转型是安全的,因为基类不会具有大于导出类的接口。但是对于向下造型,我们却无法确保其安全性。我们可以说猫是一种动物,但是不能说动物就是指猫。所以,在向下造型的过程中,必须进行一种强制型的类型转换,将基类型转换成导出类类型,才能使用导出类中的方法。那如何才能确保这种转换的安全性呢?这时就需要使用运行时类型识别了(RTTI)

运行时类型识别(RTTI):全称为Run-Time Type Identification。在java中,所有转型都会得到检查,包括向上转型,所以即使我们只是进行一次普通的加括号形式的类型转换,在进入运行期时仍然会对其进行检查,以便确保它的确是我们希望的那种类型。如果不是,就会返回一个ClassCastException(类型转换异常)。这种在运行期间对类型进行检查的行为就是RTTI。

看下面的例子来了解向下造型的使用以及RTTI所做的检查:

分析一下:图中红框中的第一部分是普通的调用,没啥好说的,第2部分基类对象引用想直接调用导出类独有方法,肯定会报错,因为基类中根本没有定义这2个方法。第三部分导出类对象向上转型为基类对象引用,调用导出类中覆盖的方法,输出的是导出类方法中的内容,第四部分,导出类对象向上转型为基类对象引用,直接调用导出类独有方法,无法调用,RTTI检查出检查出这个引用是基类类型的,所以无法调用导出类的独有方法。第5部分,使用向下造型,将指向导出类对象的基类引用赋给导出类引用,通过(导出类)基类引用这种方式转换类型,然后就可以调用到子类的独有方法了,RTTI检测出uf2引用已经被转换为MoreUseful类型的了,再将它赋给了MoreUseful引用,所以可以正常调用。

在第5步中,可以不用定义muf这个引用,直接将uf2转型就行了,像下面这样:

接下来测试一下RTTI对转换类型错误时的处理。我们再定义一个OtherUseful类来继承Useful类:

然后使用类型转换试一下,像下面这样:

由于RTTI是运行时的类型检查,所以在编译期这种转换编译期是无法得知这里的转换是否是正确的,看下输出结果:

在运行时,RTTI检查机制发现了错误,uf4引用指向的是MoreUseful对象,怎么能把它转换成OtherUseful这种类型呢,MoreUseful与OtherUseful本身又没有什么关系,所以这里抛出了一个类型转换异常。

 

RTTI的功能十分强大,这里只是简单的运用了一下,在后面的学习以及博文记录中,将会详细的了解到它。

三.方法覆盖(重写)的必要条件

关于导出类如果想覆盖到基类中的方法,以下几点准则是必须要遵从的:

1.基类中的final,private,static方法不能被覆盖;

2.覆盖方法的方法名和方法参数需要相同;

3.导出类中的覆盖方法的访问权限必须要比基类中该方法的访问权限高,也可以相同;

4.导出类覆盖方法的返回类型必须要和基类中该方法的返回类型相同或是基类方法的返回类型的协变类型(自jdk1.5以后);

5.导出类覆盖方法声明抛出的异常类型必须要比基类中该方法声明抛出的异常要小或相同。

关于第2点中的方法参数相同,解释一下,参数相同指参数的个数和参数类型必须相同。参数的个数要相同,这个没有什么问题,重点是参数的类型有点异议:

分别来测试一下各种异议下的覆盖与否关系

异议一:当参数之间具有继承关系时,方法是否覆盖?看下面的例子:

看下输出结果:

分析:输出的都是基类OverrideBase()类中方法的内容,所以导出类中的2个方法均没有覆盖基类中的方法。所以,参数之间具有继承关系时,并不认为参数类型是相同的,所以具有继承类型的方法参数的方法不是覆盖(重写)的。

异议二:方法间有单个参数和同参数类型的可变参数列表之间有无覆盖关系?

异议三:方法间有数组参数与同数组类型的可变参数列表之间有无覆盖关系?

自jdk1.5版本引进了可变参数列表这个概念以后,可变参数列表可以作为方法的参数使用,用于方法参数个数或类型不确定时的场景。可变参数列表在前面的文章《Java基础-初始化与清理-可变参数列表》中有专门学习过。

传送门:https://blog.csdn.net/weixin_41866473/article/details/110950638

那么可变参数列表作为方法参数时,方法有可以覆盖的情况吗?,对于异议二和异议三,看下面的例子:

看下输出结果:

从输出结果可以看出,异议2中答案是没有覆盖关系的,异议三中的答案是有覆盖关系的。本质上来说,因为java编译器是把可变参数列表当做数组来处理的,所以以数组和可变参数列表作为方法参数时,是允许覆盖的。

这一点虽然说可能没有实际用途,但是可以了解到这一点还是有好处的。

四.多态的总结

对于多态的系统学习基本上就到这里了,但它的运用却是无处不在的。在以后的学习与工作中我也会再去一点点的总结,如果有新的发现,我会再在博客中专门去记录的。

还是来总结一下吧,想了想还是利用《Thinking In Java》中作者的记录来总结,因为他说的确实很对。

多态意味着“不同的形式”,在面向对象的程序设计中,我们持有从基类继承而来的相同接口,以及使用该接口的不同形式:不同版本的动态绑定方法。但是如果不运用数据抽象和继承,就不可能理解甚至创建多态的例子。多态不是运用在单独的场景里的,需要结合继承以及其它众多的技术来使用它。为了在程序中有效的运用多态乃至面向对象的技术,必须拓展自己的编程视野,使其不仅包括个别类的成员和消息,而且还要包括类与类之间的共同特性以及它们之间的关系,尽管这需要极大的努力,但这样做是非常值得的,因为它可以带来很多成效:更快的程序开发过程,更好的代码组织,更好的拓展程序以及更容易的代码维护等。

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值