免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 1264 | 回复: 0
打印 上一主题 下一主题

Java的声明和初始化:细看OO程序执行的顺序 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-07-14 17:24 |只看该作者 |倒序浏览
 在介绍Java的声明和初始化的执行顺序之前,让我们先来看两个类:Base和Derived类。注意其中的whenAmISet成员变量,和方法preProcess()
public class Base   
{   
    Base() {   
        preProcess();   
    }   

    void preProcess() {}   
}
 
[color="#006699"]public [color="#006699"]class Derived [color="#006699"]extends Base   
{   
   [color="#006699"]public string whenAmISet =”set [color="#0000ff"]when declared";   

   [color="#646464"]@Override [color="#006699"]void preProcess()   
   {   
       whenAmISet = [color="#0000ff"]"set in preProcess()";   
   }   
}
 如果我们构造一个子类实例,那么,whenAmISet 的值会是什么呢?
[color="#006699"]public [color="#006699"]class Main   
{   
   [color="#006699"]public [color="#006699"]static [color="#006699"]void main(String[] args)   
   {   
       Derived d = [color="#006699"]new Derived();   
       system.out.println( d.whenAmISet );   
   }   
}
  再续继往下阅读之前,请先给自己一些时间想一下上面的这段程序的输出是什么?是的,这看起来的确相当简单,甚至不需要编译和运行上面的代码,我们也应该知道其答案,那么,你觉得你知道答案吗?你确定你的答案正确吗?
  很多人都会觉得那段程序的输出应该是“set in preProcess()”,这是因为当子类Derived
的构造函数被调用时,其会隐晦地调用其基类Base的构造函数(通过super()函数),于是基类Base的构造函数会调用preProcess()
函数,因为这个类的实例是Derived的,而且在子类Derived中对这个函数使用了override关键字,所以,实际上调用到的
是:Derived.preProcess(),而这个方法设置了whenAmISet 成员变量的值为:“set in preProcess()”。
  当然,上面的结论是错误的。如果你编译并运行这个程序,你会发现,程序实际输出的是“set when declared
”。怎么为这样呢?难道是基类Base 的preProcess()
方法被调用啦?也不是!你可以在基类的preProcess中输出点什么看看,你会发现程序运行时,Base.preProcess()并没有被调用到
(不然这对于Java所有的应用程序将会是一个极具灾难性的Bug)。
  虽然上面的结论是错误的,但推导过程是合理的,只是不完整,下面是整个运行的流程:
  ◆进入Derived 构造函数。
  ◆Derived 成员变量的内存被分配。
  ◆Base 构造函数被隐含调用。
  ◆Base 构造函数调用preProcess()。
  ◆Derived 的preProcess 设置whenAmISet 值为 “set in preProcess()”。
  ◆Derived 的成员变量初始化被调用。
  ◆执行Derived 构造函数体。
  等一等,这怎么可能?在第6步,Derived 成员的初始化居然在 preProcess()
调用之后?是的,正是这样,我们不能让成员变量的声明和初始化变成一个原子操作,虽然在Java中我们可以把其写在一起,让其看上去像是声明和初始化一
体。但这只是假象,我们的错误就在在我们把Java的声明和初始化看成了一体。在C++的世界中,C++并不支持成员变量在声明的时候进行初始化,其需要
你在构造函数中显式的初始化其成员变量的值,看起来很土,但其实C++用心良苦。
  在面向对象的世界中,因为程序以对象的形式出现,导致了我们对程序执行的顺序雾里看花。所以,在面向对象的世界中,程序执行的顺序相当的重要。
  下面是对上面各个步骤的逐条解释。
  ◆进入构造函数。
  ◆为成员变量分配内存。
  ◆除非你显式地调用super(),否则Java 会在子类的构造函数最前面偷偷地插入super() 。
  ◆调用父类构造函数。
  ◆调用preProcess,因为被子类override,所以调用的是子类的。
  ◆于是,初始化发生在了preProcess()之后。这是因为,Java需要保证父类的初始化早于子类的成员初始化,否则,在子类中使用父类的成员变量就会出现问题。
  ◆正式执行子类的构造函数(当然这是一个空函数,居然我们没有声明)。
  你可以查看《Java语言的规格说明书》中的 相关章节 来了解更多的Java创建对象时的细节。
 
               
               
               

本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/98439/showart_1996158.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP