多态

在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征。


多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来。“封装”通过合并特征和行为来创建新的数据类型。“实现隐藏”则通过将细节“私有化”把接口和实现分离开来。多态的作用则是消除类型这间的耦合关系。

再论向上转型

对象即可以作为它自己本身的类型使用,也可以作为它的基类型使用,这种把对某个对象的引用视为对其基类型的引用的做法称为向上转型。

向上转型带来的问题:忘记对像的实际类型。在继承中,能发送给导出类的对象的消息都可以发送给基类的对象,因为导出类的接口不可能比基类的更窄,所以向上转型是安全。但能发送给基类的对象的消息不一定能发送给导出类的对象,如果在导出了类中添加了新的方法,那么通过基类的引用就没办法调用导出类中添加的新的方法,即不能发送这样的消息。这正式向上转型带来的问题。

转机

将导出类的引用赋值给基类型的引用,即向上转型。如果在导出类中重写了基类的方法,那将导出类的引用赋值给基类型的引用后,通过这个引用调用某一方法,那被执行的方法是基类中定义的方法的方法体还是执行的是在导出类中重写后的方法的方法体,即编译器怎么知道基类型的引用关联的对象是基类型的对象是导出类的对象?实际上,编译器是不知道的。

方法调用绑定

将一个方法调用同一个方法主体关联起来被称为绑定。若程序在执行前进行绑定,叫前期绑定。编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。(C中就是使用前起绑定)。如果编译器有一个基类型的引用,是调用基类的方法还是导出类的方法才正确呢?可以通过后期绑定来解决这个问题。后期绑定的含义就是,在运行时根据对象的类型进行绑定。后期绑定也叫动态绑定和运行时绑定。编译器不知道对象的类型,但方法调用机制能找到正确的方法体,并加以调用。
Java中,除来static和final(private方法属于的final方法)之外,其他所有方法都是后期绑定。我们不必判断是否应该进行后期绑定——它都会发生。

产生正确行为

前期绑定,编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址。后期绑定,Java使用了一小段特殊的代码来替代绝对的地址调用。这段代码使用在对象存储的信息来计算方法的地址,根据一小段代码的内容,每个对象都可以具有不同的行为表现。当向一个对象发送消息时,该对象就能够知道对这条消息应该做些什么,从而产生正确行为。

缺陷

  • “覆盖”私有方法
    当我们在导出类中想要去重写基类的某一个private方法,在导出类添加方法后惊奇的发现好像已经重写成功。例:
public class PrivateOverride{
	private void f(){
		print("PrivateOverride private f()");
	}	
 
 	public static void main(String arg[]){
 		PrivateOverride po = new Derived();
 		po.f();
 	}
}

class Derived extends PrivateOverride{
	public void f(){
		print("Derived public f()");
	}
}

// log
PrivateOverride private f()

从上面的例子中看出,当调用导出类的构造器返回的引用赋值给基类的引用(向上转型),通过引用调用的方法在基类为private,在导出类中“重写”了,通过日志,执行的方法是基类的方法而不是导出类的方法。即导出类对基类的private的方法没有重写成功,只是在导出类中添加了一个和基类中private方法一样的方法。所以private方法不具备多态表现行为。

  1. 在导出类可以定义一个类型和名字都一样的在基类中也定义过该类型和名字的字段,尽管在基类中定义为pubilc。引用向上转型后,通过基类引用来操纵该字段,操纵的是基类中的字段,而不是导出类的字段。例:
public class PrivateOverride{
 	public int field = 5 ; 
	private void f(){
		print("PrivateOverride private f() field =  "+ field);
	}	
}

class Derived extends PrivateOverride{
	public int field = 10 ; 
	public void f(){
		print("Derived public f()  field =  "+ field);
	}
	public static void main(String arg[]){
 		PrivateOverride po = new Derived();
 		Derived de = new Derived();
 		print("po field =  "+ po.field);
 		print("de field =  "+ de.field);
 	}
}
//log
po field =  5 
de field =  10

上例子中,de实际有两个field,一个是它自己,另一个是从super继承得到的。这种情况下de想要使用spuer的field,就必须通过spuer.field显示的调用。当直接访问类的某个域时,这个访问就在编译器进行解析,所以类的字段(域)是不具备多态的。

  • 静态方法

如果某个方法是静态的,那么它的行为也不具备多态。静态方法是与类,而非与单个的对象相关联的。

只有普通的非private的方法调用可以是多态的。

构造器和多态

构造器的调用顺序

基类的构造器总是在导出类的构造过程中被调用,而且照样继承层次逐渐向上链接,所使每个基类的构造器都能得到调用。

构造器有一个特殊的任务:检查对象是否被正确地构造。导出类只能访问它自己的成员,不能访问基类的成员(通常为private)。只能基类的构造器才具有恰当的知识和权限来对自己的元素进行初始化。所以必须令所有构造器都得到调用,否则就不能正确的构造完整对象。这正是编译器为什么要强制每个导出类部分都要必须调用构造器的原因。

  • 构造器内部的多态方法的行为

构造器调用顺序的层次结构带来一个有趣两难问题。如果在一个构造器的内部调用正在构造的对象的某个动态绑定的方法,会出现什么样的现象?即在对象完全构造出来前就已经调用了某个动态绑定的方法。

如果构造器只是在构建一个对像的过程中的一个步骤,并且该对象所属的类(导出类)是这从这个构造器所属的类(基类)导出来的,那么导出部分在当前构造器(基类的构造器)正在被调用的时刻仍旧是没有被初始化的。然后,一个动态绑定的方法调用却会向外深入到继承层次结构内部,它可以调用导出类的方法。所以,如果在构造内部调用一个动态绑定的方法,而这个方法所操作的成员可能还未进行初始化,所以产生的现象无法预测,可能会带来灾难。
例:

class Glyph{
	public void draw(){
		print("Glyph.draw");
	}
	public Glyph(){
		print("Glyph() start");
		draw();
		print("Glyph() end");
	}
}

class RoundGlyph extends Glyph{
	private int radius = 1;
	public RoundGlyph(int i){
		radius = i ;
		print("RoundGlyph.radius = "+ radius);
	}
	public void draw(){
		print(" RoundGlyph.draw() radius = "+ radius);
	}
	public static void main(String arg[]){
		new RoundGlyph(5);
	}
}
//log
Glyph() start
RoundGlyph.draw() radius = 0
Glyph() end
RoundGlyph.radius = 5 

导出类重写了基类的draw()方法,并在基类构造器中调用了调用动态绑定方法draw()。当创建导出类的对象时,由于继承,基类的构造会得到调用,但在基类构造器中调用了一个动态绑定的方法,这个方法在导出类中被重写了,所以方法会调用到导出类中。但时在基类构造器被调用时,而导出类的数据是还没有得到初始化的。如上面例子的log中看到那样,在基类构造器中调用了draw()方法,执行到导出类中,打印出radius为0,虽然在RoundGlyph中定义radius时就初始化为1。这就是因为在基类构造器中调用raw()时,导出类的数据还没有得到初始化。

初始化顺序

  1. 在其他任何事物发生前,将分配给对象的存储空间初始化为二进制的零。所以基本类型中的数值型的数据的值是0,对象引用被置为null。(就算定义时直接指定了值)
  2. 调用基类构造器。这个步骤不断的反复递归下去。首先是构造器这种层次结构的根,然后是下一层导出类,知道最层的导出类。
  3. 同一个类中按照声明的顺序调用成员的初始化方法。(不同类,先试整个继承层次中的所有类的静态字段和静态子句(同类中案定义顺序),从根基类到最外层导出顺序。然后是普通成成员字段和非静态子句(同类中案定义顺序),从根基类到最外层导出顺序)。
  4. 调用导出类的构造器主体。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值