前言
csdn上有好多关于Java初始化顺序的口诀什么的,示例代码也有,但是一股脑儿地把这些灌输给读者,需要读者以极大的精力去深入理解。下面我将以几个例子逐步给大家讲明白其中的构造顺序。
1.构造器初始化
1.1自动初始化
构造器初始化在运行时刻可以调用方法或执行某些动作来确定初值,这为编程带来了更大的灵活性。
但无法阻止自动初始化的过程,他将在构造器被调用之前发生。
public class F_01{
int x; //0
F_01(){
x = 5; //5
}
}
x首先会被置0(对于所有基本类型和对象引用,包括定义是已经指定初值的变量,这种情况都成立。),然后由构造器赋5.
小结:编译器不会强制我们一定要在构造器的某个地方或者在使用之前对变量进行初始化-因为初始化早已经得到保证。
1.2初始化顺序
在类的内部,变量定义的先后顺序决定了初始化的顺序。它们会在构造器和其他普通方法之前被调用得到初始化。
class Tyre{//轮胎类
Tyre(int num){
System.out.println("tyre("+num+")");
}
}
class Car{//车类
Tyre t1 = new Tyre(1);
Car(){
System.out.println("car()");
t3 = new Tyre(13); //从这里可以看出t3进行了两次初始化
}
Tyre t2 = new Tyre(2);
void f(){
System.out.println("f()");
}
Tyre t3 = new Tyre(3);
}
public class F_06 { //主函数
public static void main(String[] args) {
Car car = new Car();
car.f();
}
}
建议大家先自己走一遍初始化的思路,答案在下:
tyre(1)
tyre(2)
tyre(3)
car()
tyre(13)
f()
在Car类中,故意把Tyre对象的初始化定义分散,以证明他们全都会在构造器和其他方法之前得到初始化。
并且由输出可知,t3这个引用会被初始化两次,一次在构造器之前,一次在调用期间
2.静态数据初始化
无论创建多少个对象,静态数据只占一份存储区域。下面的代码能帮助我们了解静态存储区域是何时初始化的
class Bowl{
Bowl(int maker){
System.out.println("Bowl("+maker+")");
}
void f1(int maker){
System.out.println("f1("+maker+")");
}
}
class Table{
static Bowl b1 = new Bowl(1);
Table(){
System.out.println("Table()");
b2.f1(1);
}
void f2(int maker){
System.out.println("f2("+maker+")");
}
static Bowl b2 = new Bowl(1);
}
class Cupboard{
Bowl b3 = new Bowl(3);
static Bowl b4 = new Bowl(4);
Cupboard(){
System.out.println("Cupboard()");
b4.f1(2);
}
void f3(int maker){
System.out.println("f3("+maker+")");
}
static Bowl b5 =new Bowl(5);
}
public class F_06 {
public static void main(String[] args) {
System.out.println("Creating new Cupboard in main");
new Cupboard();
System.out.println("Creating new Cupboard in main");
new Cupboard();
table.f2(1);
cupboard.f3(1);
}
static Table table = new Table();
static Cupboard cupboard = new Cupboard();
}
/**
Bowl(1)
Bowl(1)
Table()
f1(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard()
f1(2)
Creating new Cupboard in main
Bowl(3)
Cupboard()
f1(2)
f2(1)
f3(1)
*/
上面的例子中,Bowl类使得类的创建变得透明化,而在Table类和Cupboard类中加入了Bowl类型的静态数据成员。
注意,在静态数据成员定义前,Cupboard()类先定义了一个Bowl类型的非静态数据成员b3.
静态初始化只有在必要的时刻进行,如果不创建Table对象,也不引用Table b1和Table b2,那么静态的Bowl b1和b2都不会被创建。且静态初始化只执行唯一的一次。
3.父类初始化
继承是Java语言的三大特性之一,但是继承并不只是复制父类的接口,当创建出一个子类的对象时,该对象包括一个来自父类的子对象。这个对象和我们直接创建父类对象是一样的。二者的区别在于后者来自于外部,而前者被包装在子类的对象内部。
注意:Java会自动在子类构造器中调用父类构造器
class Paper{
Paper(){
System.out.println("paper");
}
}
class Book extends Paper{
Book(){
System.out.println("book");
}
}
public class Draft_07 extends Book{
public static void main(String[] args) {
Draft_07 draft_07 = new Draft_07();
System.out.println("draft_07");
}
}
/**结果为:
paper
book
draft_07
*/
读者会发现,构建过程是从父类向外扩散的。所以父类在导出类构造器访问它之前就已经完成了初始化。
总结
看到这大家应该对上三个相应部分的初始化都有了自己的理解。
1)按照成员变量定义顺序进行初始化。即使变量定义散布于方法定义中,他们依然在任意方法被调用前进行初始化
2)静态对象(变量)优先于非静态变量对象初始化,其中,静态对象(变量)只初始化一次,非静态对象可能多次初始化;
3)父类优先于子类进行初始化;
口诀:先父后子,变量优于块,静态优于非静态