当创建一个对象时,各种成员变量及构造方法的初始化或调用顺序是怎样的呢?
(1).如果类尚未加载,则先初始化静态成员变量和静态代码块,再初始化成员变量,最后调用相应的构造方法;
(2).如果类已经加载过了,则静态成员变量已经初始过了,静态代码块也已执行,这时只需初始化成员变量,再调用构造方法就可以了;
(3).如果类还有父类,则按(1)或(2)的顺序先初始化父类,再初始化子类。
下面以例子详细说明。
包human中定义了四个类,分别为Person、Student、DustMan、TestMain;其中,Student是Person的子类。
Person类的代码如下:
package human;
public class Person {
String name;
String gender;
//test:成员变量的初始化是否在构造方法调用之前 2017.11.01
public Person() {
System.out.println("Person constructor:Person()");
}
DustMan dustTest = new DustMan();
static {
System.out.println("Person:initialization of static code block");
}
static String citizenship = "Chinese"; //test:静态成员变量与静态代码块的初始化顺序 2017.11.01
//test:子类用super显式调用父类构造方法时,各变量及构造方法初始化或调用顺序 2017.11.01
public Person(String n, String g) {
this.name = n;
this.gender = g;
System.out.println("Person constructor:Person(n,g)");
}
}
Student类的代码如下:
package human;
public class Student extends Person {
String stuNumber;
int score;
static String testIni = "test";
static {
System.out.println("Student:testIni = " + testIni);
}
//test:成员变量的初始化是否在构造方法调用之前 2017.11.01
public Student() {
System.out.println("Student constructor:Student()");
}
//test:子类用super显式调用父类构造方法时,各变量及构造方法初始化或调用顺序 2017.11.01
public Student(String n, String g) {
super(n,g);
System.out.println("Student constructor:Student(n,g)");
}
}
DustMan类的代码如下:
package human;
public class DustMan {
public DustMan() {
System.out.println("DustMan constructor");
System.out.println("change value of the static variable citizenship " + "\"" + Person.citizenship + "\"");
Person.citizenship = "US";
System.out.println(" to " + "\"" + Person.citizenship + "\"");
}
}
TestMain类的代码如下:
package human;
public class TestMain {
public static void main(String[] args) {
Student stuTest = new Student();
Student stuTest2 = new Student("liu","female");
System.out.println("citizenship = " + Person.citizenship);
}
在TestMain类中执行,输出结果如下:
Person:initialization of static code block
Student:testIni = test
DustMan constructor
change value of the static variable citizenship "Chinese"
to "US"
Person constructor:Person()
Student constructor:Student()
DustMan constructor
change value of the static variable citizenship "US"
to "US"
Person constructor:Person(n,g)
Student constructor:Student(n,g)
citizenship = US
下面对输出结果进行分析:
(1).执行第一条语句,创建对象stuTest,Student stuTest = new Student();
<1>.输出的第1行:Person:initialization of static code block
可以看出,虽然创建的是Student类对象,但先加载的是父类,加载时执行Person类的静态代码块,有第一行输出。
<2>.输出的第2行:Student:testIni = test
加载过父类后,并没有接着初始父类的成员变量及调用构造方法,而是接着加载了子类Student,加载时执行Student的静态代码块,有第二行输出。
<3>.输出的第3行:DustMan constructor
Person类和Student类都加载后,开始初始化Person类的成员变量,有一个变量是DustMan类型的,即DustMan dustTest = new DustMan();dustTest的定义虽然在无参构造方法的后面,但根据输出结果看,先初始化的对象dustTest,后调用的构造方法,也就是说成员变量的初始化在构造方法的调用之前。
<4>,输出的第4行和第5行:change value of the static variable citizenship "Chinese"
to "US"
可以看出,在Person的构造方法调用之前,静态成员变量citizenship已经被初始为“Chinese”了,在DustMan的构造方法中又被修改为“US”。
<5>.输出的第6行:Person constructor:Person()
Person类的成员变量初始化完成后,接着调用构造方法Person()。
<6>.输出的第7行:Student constructor:Student()
Person类构造方法调用后,接着初始化子类成员变量,并调用构造方法。
(2).第一条语句已经执行完,接着执行第二条语句Student stuTest2 = new Student("liu","female");
<7>.输出的第8行:DustMan constructor
很明显,创建第二个Student对象stuTest2时,父类和子类的静态代码块都没有再执行,直接初始化成员变量。
<8>输出的第9行和第10行:change value of the static variable citizenship"US"
to "US"
citizenship的值由第<4>步改为“US”后就没有发生变化,说明静态成员变量也没有再次初始化为"Chinese"。
<9>.输出的第11行:Person constructor:Person(n,g)
父类的成员变量初始化后,接着调用构造方法,这次调用的不是无参构造方法,而是Person(n,g);Student的构造方法Student(n,g)里用super(n,g)显式的调用了父类的构造方法,说明要调用父类哪个构造方法是由子类的构造方法决定的。
<10>.输出的第12行:Student constructor:Student(n,g)
父类构造方法调用后,接着初始化子类成员变量,并调用子类构造方法。
<11>.输出的第13行:citizenship = US
再次确认citizenship的值没有重新初始化。
(3).在Person的静态代码块中添加一行System.out.println("citizenship = " + citizenship);,如下所示:
static {
System.out.println("Person:initialization of static code block");
System.out.println("citizenship = " + citizenship);
}
static String citizenship = "Chinese"; //test:静态成员变量与静态代码块的初始化顺序 2017.11.01
出现Cannot reference a field before it is defined的错误,说明静态代码块的执行和静态成员变量的初始化是按定义的顺序执行的。
总结:
(1).首次使用类时,先加载类,加载时初始类的静态成员变量,执行静态代码块;
静态成员变量和静态代码块的顺序按定义的顺序来;
静态成员变量和静态代码块都只在类加载时初始化或执行一次。
(2).如果有父类,则先加载父类,再加载子类。
(3).new一个对象时,父类子类都加载后,先初始化父类的成员变量,再调用父类的构造方法;
调用父类的哪个构造方法要看new时调用的子类的哪个构造方法,看子类的这个构造方法是如何使用super(参数),执行与super的参数一致的父类构造方法。
(4).父类调用过构造方法后,再初始化子类成员变量,再调用子类的构造方法。