构造函数初始化:Java构造时成员初始化的陷阱

让我们先来看两个类:Base和Derived类注意其中whenAmISet成员变量和思路方法preProcess



1. public Base
2. {
3. Base {
4. preProcess;
5. }
6.
7. void preProcess {}
8. }


01. public Derived extends Base
02. {
03. public String whenAmISet = " when declared" ;
04.
05. @Override void preProcess
06. {
07. whenAmISet = " in preProcess" ;
08. }
09. }
如果我们构造个子类例子那么whenAmISet 值会是什么呢?



1. public Main
2. {
3. public void (String args)
4. {
5. Derived d = Derived;
6. .out.prln( d.whenAmISet );
7. }
8. }
再续继往下阅读的前请先给自己些时间想下上面这段输出是什么?是这看起来确相当简单甚至不需要编译和运行上面代码我们也应该知道其答案那么你觉得你知道答案吗?你确定你答案正确吗?

很多人都会觉得那段输出应该是“ in preProcess这是当子类Derived 构造其会隐晦地其基类Base构造(通过super)于是基类Base构造preProcess 这个类例子是Derived而且在子类Derived中对这个使用了override关键字所以实际上是:Derived.preProcess而这个思路方法设置了whenAmISet 成员变量值为:“ in preProcess

当然上面结论是如果你编译并运行这个你会发现实际输出是“ when declared ”如何为这样呢?难道是基类Base preProcess 思路方法被啦?也不是!你可以在基类preProcess中输出点什么看看你会发现运行时Base.preProcess并没有被到(不然这对于Java所有应用将会是个极具灾难性Bug)

虽然上面结论是但推导过程是合理只是不完整下面是整个运行流程:

进入Derived 构造
Derived 成员变量内存被分配
Base 构造被隐含
Base 构造preProcess
Derived preProcess 设置whenAmISet 值为 “ in preProcess
Derived 成员变量化被
执行Derived 构造
这如何可能?在第6步Derived 成员化居然在 preProcess 的后?是正是这样我们不能让成员变量声明和化变成个原子操作虽然在Java中我们可以把其写在让其看上去像是声明和但这只是假象我们就在于我们把Java中声明和化看成了在C世界中C并不支持成员变量在声明时候进行其需要你在构造中显式化其成员变量看起来很土但其实C用心良苦

在面向对象世界中以对象形式出现导致了我们对执行顺序雾里看花所以在面向对象世界中执行顺序相当重要

下面是对上面各个步骤逐条解释

进入构造
为成员变量分配内存
除非你显式地super否则Java 会在子类构造最前面偷偷地插入super
父类构造
preProcess被子类override所以是子类
于是化发生在了preProcess的后这是Java需要保证父类化早于子类成员否则在子类中使用父类成员变量就会出现问题
正式执行子类构造(当然这是个空居然我们没有声明)
你可以查看Java语言规格介绍说明书 相关章节 来了解更多Java创建对象时细节

最后需要向大家推荐本书Joshua Bloch 和 Neal Gafter 写 Java Puzzlers: Traps, Pitfalls, and Corner Cases 中文版JAVA解惑
Tags:  java初始化 javamap初始化 java数组初始化 构造函数初始化

延伸阅读

最新评论

发表评论