标签:
转自:http://coolshell.cn/articles/1106.html
让我们先来看一段代码:
1 public class Test { 2 public static void main(String[] args) { 3 SubClass subClass = new SubClass(); 4 System.out.println(subClass.whenAmISet); 5 } 6 } 7 8 class ParentClass { 9 ParentClass() { 10 print(); 11 } 12 13 void print() { 14 System.out.println("I‘m ParentClass"); 15 } 16 } 17 18 class SubClass extends ParentClass { 19 public String whenAmISet = "set when declared"; 20 21 // SubClass() { 22 // super(); 23 // } 24 25 @Override 26 void print() { 27 System.out.println("I‘m SubClass"); 28 whenAmISet = "set in print()"; 29 } 30 }
在继续往下阅读之前,请先给自己一些时间想一下上面的这段程序的输出是什么?是的,这看起来的确相当简单,甚至不需要编译和运行上面的代码,我们也应该知道其答案,那么,你觉得你知道答案吗?你确定你的答案正确吗?
很多人都会觉得那段程序的输出应该是“set in print()”,这是因为当子类SubClass 的构造函数被调用时,其会隐晦地调用其基类ParentClass的构造函数(通过super()函数),于是基类ParentClass的构造函数会调用print() 函数,因为这个类的实例是SubClass的,而且在子类SubClass中对这个函数使用了override关键字,所以,实际上调用到的是:SubClass.print(),而这个方法设置了whenAmISet 成员变量的值为:“set in print()”。
当然,上面的结论是错误的。如果你编译并运行这个程序,你会发现,程序实际输出的是“set when declared ”。怎么为这样呢?难道是基类ParentClass 的print() 方法被调用啦?也不是!你可以在基类的print中输出点什么看看,你会发现程序运行时,ParentClass.print()并没有被调用到(不然这对于Java所有的应用程序将会是一个极具灾难性的Bug)。
其实,代码的输出是:
I‘m SubClass set when declared
虽然上面的结论是错误的,但推导过程是合理的,只是不完整,下面是整个运行的流程:
等一等,这怎么可能?在第6步,SubClass 成员的初始化居然在 print() 调用之后?是的,正是这样,我们不能让成员变量的声明和初始化变成一个原子操作,虽然在Java中我们可以把其写在一起,让其看上去像是声明和初始化一体。但这只是假象,我们的错误就在于我们把Java中的声明和初始化看成了一体。在C++的世界中,C++并不支持成员变量在声明的时候进行初始化,其需要你在构造函数中显式的初始化其成员变量的值,看起来很土,但其实C++用心良苦。
在面向对象的世界中,因为程序以对象的形式出现,导致了我们对程序执行的顺序雾里看花。所以,在面向对象的世界中,程序执行的顺序相当的重要。
下面是对上面各个步骤的逐条解释。
你可以查看《Java语言的规格说明书》中的相关章节来了解更多的Java创建对象时的细节。
C++的程序员应该都知道,在C++的世界中在“构造函数中调用虚函数”是不行的,Effective C++ 条款9:Never call virtual functions during construction or destruction,Scott Meyers已经解释得很详细了。
在语言设计的时候,“在构造函数中调用虚函数”是个两难的问题。
C++选择了第一种,而Java选择了第二种。
标签:
原文地址:http://www.cnblogs.com/xianDan/p/4292951.html