Java初始化(构造函数)
来源:互联网 发布:坐牛网络 编辑:程序博客网 时间:2024/05/16 09:05
//先来看两个类:Base和Derived类。注意其中的whenAmISet成员变量,和方法preProcess() public class Base { Base() { preProcess(); } void preProcess() {} public static void main(String[] args) { Derived d = new Derived(); System.out.println( d.whenAmISet ); } } class Derived extends Base { public String whenAmISet = "set when declared" ; @Override void preProcess() { whenAmISet = "set in preProcess()" ; } } //我们构造一个子类实例,那么,whenAmISet 的值会是什么呢?/*set when declared请按任意键继续. . .*/
//先来看两个类:Base和Derived类。注意其中的whenAmISet成员变量,和方法preProcess() public class Base { Base() { preProcess(); } void preProcess() {} public static void main(String[] args) { Derived d = new Derived(); System.out.println( d.whenAmISet ); } } class Derived extends Base { public String whenAmISet ;//= "set when declared" ; @Override void preProcess() { whenAmISet = "set in preProcess()" ; } } //我们构造一个子类实例,那么,whenAmISet 的值会是什么呢?/*set when declared请按任意键继续. . .*//*set in preProcess()请按任意键继续. . .*/
再续继往下阅读之前,请先给自己一些时间想一下上面的这段程序的输出是什么?是的,这看起来的确相当简单,甚至不需要编译和运行上面的代码,我们也应该知道其答案,那么,你觉得你知道答案吗?你确定你的答案正确吗?
很多人都会觉得那段程序的输出应该是“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创建对象时的细节。
C++的程序员应该都知道,在C++的世界中在“构造函数中调用虚函数”是不行的,Effective C++ 条款9:Never call virtual functions during construction or destruction,Scott Meyers已经解释得很详细了。
在语言设计的时候,“在构造函数中调用虚函数”是个两难的问题。
如果调用的是父类的函数的话,这个有点违反虚函数的定义。
如果调用的是子类的函数的话,这可能产生问题的:因为在构造子类对象的时候,首先调用父类的构造函数,而这时候如果去调用子类的函数,由于子类还没有构造完成,子类的成员尚未初始化,这么做显然是不安全的。
C++选择了第一种,而Java选择了第二种。
C++类的设计相对比较简陋,通过虚函数表来实现,缺少类的元信息。
而Java类的则显得比较完整,有super指针来导航到父类。
最后,需要向大家推荐一本书,Joshua Bloch 和 Neal Gafter 写的 Java Puzzlers: Traps, Pitfalls, and Corner Cases ,中文版《JAVA解惑 》。
(转载时请注明作者和出处,请勿用于商业用途 )
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/haoel/archive/2009/07/03/4319793.aspx
- Java初始化(构造函数)
- java 初始化和构造函数
- (C++)构造函数初始化
- java初始化构造函数调用顺序
- Java之构造函数与初始化块
- Java之构造函数与初始化块
- java-构造函数和静态初始化
- java初始化构造函数调用顺序
- java初始化构造函数调用顺序
- java构造函数非静态初始化
- java含参构造函数初始化
- java类的初始化和构造函数
- java初始化构造函数调用顺序
- java初始化构造函数调用顺序
- 构造函数+初始化函数
- Java 代码块、成员变量初始化、构造函数初始化顺序
- Java中的初始化顺序(静态成员、静态初始化块,普通成员、普通初始化块、构造函数)
- 三角类(构造函数初始化)
- Android中在ScrollView中显示ListView
- win7下使用PHP Expert Editor编写php程序,传入已经搭配好LAMP环境的Ubuntu内的服务器上
- 查询域名所指向的IP地址
- C#控件
- 常量成员函数
- Java初始化(构造函数)
- JAVA接口联调
- SQLServer2000同步复制技术实现步骤
- 如何成为优秀的程序员
- Double运算误差
- 主流浏览器初识
- Linux环境下如何调试live555
- velocity的基本语法
- 在eclipse打开项目工程文件所在的目录