thinkinginjava学习笔记07_多态
在上一节的学习中,强调继承一般在需要向上转型时才有必要上场,否则都应该谨慎使用;
向上转型和绑定
向上转型是指子类向基类转型,由于子类拥有基类中的所有接口,所以向上转型的过程是安全无损的,所有对基类进行的操作都可以同样作用于子类;如示例代码中,Music.tune方法调用时,需要的参数是基类Instrument,而传入一个子类:Wind类的对象时,该方法一样可以被调用,并且play方法执行的是Wind类的对象重载的方法;
在向上转型的设计中,只编写和基类打交道的代码,这样所有的特定子类都可以正确使用该方法,而不用针对每一个子类都编写特定的代码;在一个经典的例子中(示例代码):一个基类Shape派生出很多子类(各种形状),每个子类都对基类的接口进行了重载,工厂类在生成子类时,返回的是基类对象(准确的说是子类对象实体的基类对象引用),而创建的实际对象则是子类对象;由工厂创建的对象调用重载方法时,依然可以正确调用子类重载的方法;这是由于Java中,除了static和final(包括private),所有的对象都是后期绑定,也就是在编译时,执行的方法并不知道执行主体的真正类型,只有当执行的时候才会确定该对象的真正类型,虽然工厂创建的是基类对象引用,当调用该对象的方法时,由于该引用指向的实体子类对象会和该方法完成绑定,并被解释器解释;
缺陷
但是,由于static和final(包括private)方法是前期绑定的,也就是在编译时,方法就和类型进行了绑定,只能调用绑定类型的方法;如在示例代码中,PrivateOverride po = new Derived();虽然子类Derived中有新的方法:f(),但是由于基类中的f()是private,对子类是不可见的,所有子类并没有实现重载,而只是写了一个同名的方法而已;由于PrivateOverride类中,f()方法是private,在编译时,po.f();调用一个基类引用的private方法,编译器执行了基类对象和f()方法的绑定,所以虽然对象实体是Derived对象,但是执行po.f()时,仍然执行的是基类中的方法;
这里就会有一个陷阱,由于private是对使用者不可见的,所以并不能知道继承的基类中是否有某种private方法,如果在子类中实现同名方法,并且使用基类引用来引用子类对象实体的话,子类中实现的同名方法就不会得到正确调用;
除了private,相似的问题出现在对静态方法和域(数据对象)的访问时,如示例代码中,基类引用sup和子类引用sub,虽然对象实体都是Sub对象,但是两个引用访问得到的field却是完全不同的;并且sub引用的对象其实包含了两个成为field的域,但是直接调用时的默认field是Sub版本中的field,如果想要调用Super版本中的field,则必须显式地使用super.field调用;但是域的访问一般不会出错,因为通常都会将域设为private,此时,sup引用的对象实体是Sub,并不能完成对private field的调用,只能通过getField方法进行获取,而由于getField方法并不是final的,此时就避免了该问题;
而静态方法则由于是只和类相关联,所以也并不具备多态性;
构造器和多态
通过一个例子来复习继承中构造器的执行顺序以及潜在的问题:示例代码;输出结果为:
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 5
执行new RoundGlyph(5);时,初始化过程为:
1. 在任何事物发生之前,将分配给对象的存储空间初始化为二进制0;(较之前新增);
2. 加载基类Glyph,并且Glyph并没有其他基类,执行Glyph的static初始化(并没有),执行Glyph构造器;打印
Glyph() before draw()
调用draw()方法,此时由于RoundGlyph中对draw()进行了重载,所以将调用重载的draw()方法,此时RoundGlyph并没有完成初始化,由于第一步分配,radius=0,故打印:
RoundGlyph.draw(), radius = 0
然后打印:
Glyph() after draw()
3. 加载子类RoundGlyph,执行static初始化,radius=1,执行RoundGlyph构造器,并打印
RoundGlyph.RoundGlyph(), radius = 5
在这个过程中,在构造器中添加了后期绑定的方法:draw(),导致执行出现逻辑上的问题,由上例可以总结出构造器的编写准则:
用尽可能简单的方法使对象进入正常状态;如果可以的话,避免使用其他方法;构造器中唯一可以调用的方法是基类中的final方法;
协变返回类型
在Java SE5之后,子类中的重载方法可以返回基类方法的返回类型的某种子类;如:示例代码中:
Mill m = new Mill();
Grain g = m.process();
println(g);
m = new WheatMill();
g = m.process();
println(g);
由于m是一个Mill类的引用,m = new WheatMill()表示一个Mill的引用引用了WheatMill对象(向上转型),而process()方法实现了重载,而在JavaSE5中添加的规则,该方法可以返回为Grain的子类Wheat,因此,m.process()返回的是一个Wheat对象实体,用一个Grain类的引用g来引用该实体;
而在这之前,m.process()的返回值将强制返回为Grain,而不能返回为Wheat;
继承设计
这个问题在前一篇随笔中也有提到,这设计时,应该优先使用组合的方式,而继承应该在需要被向上转型时用到;一条通用准则是:用继承来表达行为之间的差异,并用字段(即组合)来表达状态上的变化;如:示例代码中,Stage类中包含一个基类Actor的引用,并初始化为HappyActor,但是change方法可以改变该引用指向的具体对象实体,比如程序中将其改变为Actor的另外一个子类:SadActor中,此时就完成了状态的变化,Stage类的实例化对象相应的行为都统一做了改变;
(很cool!)
在继承设计时,只继承基类中的已有的方法,这样做可以避免一些继承带来的问题,而把子类看做是基类的一个替代(is-a关系),二者具有完全相同的接口;
但是在实际设计时,扩展接口是难以避免的,此时,子类中不仅仅有基本接口,还有一些额外方法实现的其他特性(is-like-a关系);此时,子类中的扩展部分并不能被基类访问,并且一旦完成向上转型,则不能继续调用扩展部分,如示例代码中,x[1].u()会产生错误;从这里也可以看到,虽然x[1]的对象实体是MoreUseful,并且执行重载方法时,方法是和MoreUseful对象完成后期绑定,但是x[1]仍然是一个Useful的引用,并不能调用任何Useful类接口之外的方法,否则将会产生类转型异常;
thinkinginjava学习笔记07_多态的更多相关文章
- SQL反模式学习笔记7 多态关联
目标:引用多个父表 反模式:使用多用途外键.这种设计也叫做多态关联,或者杂乱关联. 多态关联和EAV有着相似的特征:元数据对象的名字是存储在字符串中的. 在多态关联中,父表的名字是存储在Issue_T ...
- No2_3.接口继承多态_Java学习笔记_多态
***多态***1.多态性:通常使用方法的重载(Overloading)和重写(Overriding)实现类的多态:2.重写之所以具有多态性,是因为父类的方法在子类中被重写,方法名相同,实现功能不同. ...
- thinkinginjava学习笔记01_导论
初学java,希望旅途愉快 :) 类型决定对象的接口,(有人认为类是类型的特定实现),接口确定对象所能发出的请求(消息),满足请求的代码和隐藏的数据一起构成实现: 对象设计时,应该很好地完成一项任务 ...
- 1.12(java学习笔记)多态及向上、向下转型
一.多态 多态是指同一个方法被调用,由于对象不同导致行为不同. 例如调用自由活动方法,张三喜欢玩耍,那么他就会去玩耍. 李四喜欢学习,那么他可能去学习.调用方法因对象的不同 而产生了不同的行为. 形成 ...
- Thinking in java学习笔记之多态
多态是一种将改变的事物和未变的事物分离开来的重要技术.
- Java学习笔记之多态
1.父类型的引用可以指向子类型的对象: Parent p = new Child(); 2.当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误:如果有,再去调用子类的该同名方法 ...
- thinkinginjava学习笔记06_复用类
MarsEdit粘代码好麻烦,所有代码交给github:https://github.com/lozybean/MyJavaLearning 复用一个类常用的两种方式:组合.继承: 组合 将对象引用置 ...
- thinkinginjava学习笔记04_初始化与清理
java沿用了c++的构造器,使用一个和类名完全一样的方法作为类的构造器,可以有多个构造器来通过不同的参数进行构造,称为重载:不仅是构造器可以重载,其他方法也一样通过不同的形参以及不同的返回值来实现重 ...
- C++学习笔记:多态篇之虚析构函数
动态多态中存在的问题:可能会产生内存泄漏! 以下通过一个例子向大家说明问什么会产生内存泄漏: class Shape//形状类 { public: Shape(); virtual double ca ...
随机推荐
- php银联网页支付实现方法
本文实例讲述了php银联网页支付实现方法.分享给大家供大家参考.具体分析如下: 这里介绍的银联WAP支付功能,仅限消费功能. 1. PHP代码如下: 复制代码代码如下: <?phpna ...
- UWP 共享文件——发送者
这一节,顾名思义,即使你要共享数据给别人,你是数据的提供者.分两步即可1.直接复制代码 protected override void OnNavigatedTo(NavigationEventArg ...
- jQuery noConflict() 方法----与其他javaScript插件冲突时
1,全名代替----jQuery: $.noConflict(); jQuery(document).ready(function(){ jQuery("button").clic ...
- 团队合作-如何避免JS冲突
解决JS冲突的演化过程 1.用匿名函数将脚本包裹起来,可以有效控制全局变量,避免冲突隐患 (function(){})(): 2.定义一个全局作用域的变量str,可以帮助我们在不同匿名函数间通信 严格 ...
- Cs Round#54 E Late Edges
题意:给定一个无向图,你从结点1开始走,每经过一条边需要1的时间,每条边都有一个开放时间,只有当目前所用的时间大于等于开放时间时,这条边才可以被经过.每一单位时间你都必须经过一条边,问最快什么时候可以 ...
- 树莓派搭建pptp---vpn
好久没写博文了啊,这次好好写 先普及下知识啊 PTP(Point to Point Tunneling Protocol),即点对点隧道协议.该协议是在PPP协议的基础上开发的一种新的增强型安全协议, ...
- JavaWeb学习总结(三)——Tomcat服务器学习和使用(二)(转)
转载自 http://www.cnblogs.com/xdp-gacl/p/3744053.html 一.打包JavaWeb应用 在Java中,使用"jar"命令来对将JavaWe ...
- Git版本号控制 为什么那么复杂 头大 (忍不住强烈吐槽)
想把自己的源代码保存到云端.想到了用Github.com,然后便開始看怎么使用GIT. 一開始,没有接触之前,想的非常easy的.应该就跟SVN几乎相同吧.写好了提交就能够了. 只是使用了之后才发现根 ...
- Winform开发框架中工作流模块之审批会签操作
在前面介绍了框架中工作流的几个开发过程,本篇随笔重点介绍一下日常审批环节中的具体处理过程,从开始创建表单,以及各个审批.会签过程的流转过程,希望大家对其中流程的处理有一个大概的印象. 1.请假申请表单 ...
- Effective Java 第三版——6. 避免创建不必要的对象
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...