小弟有一个java中初始化成员的问题Thinking in Java 这本书中说过,
所有的成员变量都是在调用构造函数之前进行初始化的
但是在继承中为什么就不是这种情况了呢?代码如下:class Test{
Test(){
System.out.println("The TestClass Print!");
}
}class FatcherClass{
public long test;
FatcherClass(){
System.out.println("The FatcherClass Print! The Value = " + test);
}
}
class SunClass extends FatcherClass{
SunClass(){
System.out.println("Test SunClass Print!");
}
}public class ExtendsClassTest extends SunClass { private Test t = new Test();
ExtendsClassTest(){
System.out.println("The ExtendsClassTest Print!");
}
public static void main(String[] ages){
ExtendsClassTest sClass = new ExtendsClassTest();
}
}上述的执行结果为什么是
The FatcherClass Print! The Value = 0
Test SunClass Print!
The TestClass Print!
The ExtendsClassTest Print!而不是
The TestClass Print!
The FatcherClass Print! The Value = 0
Test SunClass Print!
The ExtendsClassTest Print!
这个结果呢?????
希望各位大大们帮着解释下
所有的成员变量都是在调用构造函数之前进行初始化的
但是在继承中为什么就不是这种情况了呢?代码如下:class Test{
Test(){
System.out.println("The TestClass Print!");
}
}class FatcherClass{
public long test;
FatcherClass(){
System.out.println("The FatcherClass Print! The Value = " + test);
}
}
class SunClass extends FatcherClass{
SunClass(){
System.out.println("Test SunClass Print!");
}
}public class ExtendsClassTest extends SunClass { private Test t = new Test();
ExtendsClassTest(){
System.out.println("The ExtendsClassTest Print!");
}
public static void main(String[] ages){
ExtendsClassTest sClass = new ExtendsClassTest();
}
}上述的执行结果为什么是
The FatcherClass Print! The Value = 0
Test SunClass Print!
The TestClass Print!
The ExtendsClassTest Print!而不是
The TestClass Print!
The FatcherClass Print! The Value = 0
Test SunClass Print!
The ExtendsClassTest Print!
这个结果呢?????
希望各位大大们帮着解释下
在调用构造方法之前保证所有的initialization都已经做完。
class Test {
Test() {
System.out.println("The TestClass Print!");
}
}class FatcherClass {
public long test; FatcherClass() {
System.out.println("The FatcherClass Print! The Value = " + test);
}
}class SunClass extends FatcherClass { SunClass() {
System.out.println("Test SunClass Print!");
}
}public class ExtendsClassTest extends SunClass { private Test t = new Test(); ExtendsClassTest() {
System.out.println("The ExtendsClassTest Print!");
} public static void main(String[] ages) {
ExtendsClassTest sClass = new ExtendsClassTest();
}}在继承中,首先得执行这个子类的父类的构造函数,如果子类中有成员变量,那么将在子类的构造函数的前面进行初始化.
如果不涉及继承跟static,用new创建对象时,会调用那个类的构造函数,而在调用构造函数之前必需先初始化域(因为在构造函数里,可能会用到这些成员变量)
等域初始化完后再调用构造函数。强调一点:只要是成员变量,那么不管它放在类的哪个部位(但在方法或块内部不算,因为那算是局部变量),它都在构造函数调用之前调用,这是编译器确保的。 2.
如果涉及继承,当用new创建子类对象时,调用顺序是这样的:
1.先初始化父类的域(成员变量或块)
2.调用父类的构造函数(没有明确定义的话,调用默认那个,即编译器为你创建的)
3.再到子类,初始化子类的域
4.这时才轮到调用子类的构造函数
原则是:要确保域在被调用之前要被初始化.
上面是涉及两层,如果是涉及多层继承的,那么一致递推上去,即先初始化父类的域,然后调用父类构造函数,再初始化子类的域然后再调用子类的构造函数,再初始化子子类的域(用这个名字好像有点怪,哈哈,就是孙子类的意思)然后再调用子子类的构造函数,一致类推下去
3.涉及static的话,static域是在编译的时候加载的,原则是:
1.static域是在非static(上面说的都是非static)之前调用的
2.static域只初始化一次(即只调用一次),打个比方A a = new A(); A 里有static域,只有当你第一次使用new创建对象的时候它会在非static之前调用,而如果你还想再用new创建对象时,static域这段代码是不会被调用的(因为static的东西是属于类,所以对象共享的,一次就够了) 4.如果涉及继承跟static结合的话(而这个是初始化里最难的,很多初学者会卡在这里),只要按照3.2结合就行了。
所以,成员变量和构造函数是有先后,但父子关系的先后是大前提。这也是作为一个class的历史局限性吧,呵呵。
在java中有四块区域1:static data segment(存放static数据和static方法...);
2:stack segment (速度比较快,存放临时的数据);
3:code segment(存放Class文件)
4:heap(存放new出来的类 垃圾产生和回收的地方)在程序运行以前 先加载 static 的域 在static data segment里面开辟内存
最典型的就是public static void main(String []arg){}
所以一般认为是从面开始运行的,但是并不完全是这样 5楼说的比较详细,也比较全面你就这样理解吧: 先实现父类的静态域,静态方法(必须调用才能动态执行)---然后再就是父类的一般域 和方法(方法必须调用才能动态执行)
接着就是子类的静态域,静态方法(必须调用才能动态执行)---然后再就是子类的一般域 和方法(方法必须调用才能动态执行)至于前面4个块 的话 你可以在维基百科里面查我就点到为止
养成自己动手解决问题的好习惯!这个很重要!希望我的解释对你有用!
static{
System.out.println("Test2.静态块");
}
{
System.out.println("Test2.程序块");
}
public Test2(){
System.out.println("Test2.构造方法");
}
Other o=new Other("Test2");
public static void main(String [] args){
Test2 test=new Test2();
}}class Father extends Ffather{
static{
System.out.println("Father.静态块");
}
Other o=new Other("Father");
{
System.out.println("Father.程序块");
}
Father(){
System.out.println("Father.构造方法");
}
}class Ffather{
static{
System.out.println("Ffather.静态块");
}
Other o=new Other("Ffather");
{
System.out.println("Ffather.程序块");
}
Ffather(){
System.out.println("Ffather.构造方法");
}
}class Other{
static{
System.out.println("Other.静态块");
}
{
System.out.println("Other.程序块");
}
Other(String str){
System.out.println("Other.构造方法 "+str+"调用");
}
}//执行结果
Ffather.静态块
Father.静态块
Test2.静态块
Other.静态块
Other.程序块
Other.构造方法 Ffather调用
Ffather.程序块
Ffather.构造方法
Other.程序块
Other.构造方法 Father调用
Father.程序块
Father.构造方法
Test2.程序块
Other.程序块
Other.构造方法 Test2调用
Test2.构造方法
首选在用new关键字后跟构造函数的语句生成实例的时候会经过以下过程。
首先虚拟机会检查生成实例的类有没有被初始化(前提是这个类已经被虚拟机装载,如果装载失败此时应该会抛出classnotfoundexception异常),如果没有被初始化,虚拟机会检查类有没有初始化方法(类初始化方法,即"<clinit>"方法,这是一个由编译器产生,且只能由虚拟机内部调用的静态方法,这个方法并不是所有类都有,是只有初始化语句的类才有这个方法的,初始化语句就是静态字段=后面的表达式语句以及static语句块中的语句,编译器会将这些语句添加到"clinit"方法中),如果有初始化方法就调用初始化方法,如果没有则跳过这个过程。在此之前,虚拟机会检查这个类的父类有没有被初始化,如果父类没有被初始化,先初始化父类。
类初始化以后,接着就是给生成的对象分配内存空间,分配完成以后执行对象的构造函数,很多人会疑惑,这个地方为什么没有说先去执行父类构造函数,或者说先初始化对象的实例字段,因为很简单,在进入当前类构造函数的时候的第一个语句就是调用父类构造函数,除了java.lang.Object类的构造函数以外,所有构造函数都是如此。然后执行构造函数内的初始化语句。这里又有人疑问,那类的字段初始化与初始化块在哪里,其实这些语句就在构造函数内。编译器在编译java文件生成class的时候,会将字段初始化语句,初始化块以及构造函数内的语句复制到构造函数内,而且是有一定顺序的,首先将把初始化字段语句与初始化块复制到调用父类构造函数之下,紧接着在吧当前构造函数语句(.java文件中的构造函数居于)复制到构造函数内(.class文件的构造函数),而字段与语句块的执行顺序则是.java文件中他们出现的顺序(这里你不妨实验一下,看看在某个字段的=操作符后面能不能使用在其后面声明的字段,很明显就是不能的,编译无法通过)。
经过以上过程,就生成一个实例了。
父类静态属性(方法),子类静态属性(方法)
父类成员属性(方法),子类属性(方法)
父类构造属性(方法),子类属性(方法)
分别是Test.class FatcherClass.class SunClass.class ExtendsClassTest.class
step2:java ExtendsClassTest
试图加载ExtendsClassTest.class,然而由于有父类SunClass(通过extends关键字得知),此时,
“中断加载”ExtendsClassTest.class,立马加载SunClass.class,然而也由于它也有父类FatcherClass,此时,“中断加载”SunClass.class,立马加载FatcherClass。
实际上,加载类文件顺序为:FatcherClass.class,SunClass.class,ExtendsClassTest.class.