当new一个对象,特别是含有继承关系的对象时,java初始化的步骤是什么?首先明确java分类初始化< clinit >() 和 实例初始化< init >()。
java的类定义中,静态变量及其赋值语句、静态代码块、静态方法属于类的范畴,他们的初始化在< clinit >()时进行。而变量,代码块、方法等属于实例的范畴,他们的初始化在< init >()时进行。
在new一个对象时,如果类还没有被初始化,首先进行类的初始化。在类的初始化过程中:
- 虚拟机会保证在子类的< clinit >()执行之前,父类的< clinit >()先执行。因此,在虚拟机中第一个被执行的< clinit >()一定是java.lang.Object。
- < clinit >()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的,编译器收集的顺序是根据其在源文件中的顺序决定的,静态语句块中仅能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句中可以赋值,但不能访问。例如
public class Test{
static{
i = 0; //合法
//System.out.println(i);//不能引用,不合法
}
public static int i = 1;
public static void main(String [] args){
System.out.println(i);
}
}
此时输出的i值应该为1,因为等于1的赋值语句在后面。
又如:
static class Parent{
public static int a = 1;
static {
a = 2;
}
}
static class Sub extends Parent{
public static int b = a;
}
public static void main(String [] args){
System.out.println(Sub.b);
}
由于Parent将在Sub之前初始化,即a=2将在public static int b = a; 这句之前执行,因此,输出b的值应为2。
在类初始化完之后,进行实例的初始化< init >():
例如:
public class Parent{
public static String pStaticStr = "parent static string";
public String pStr = "parent instance string";
static{
System.out.println("parent static fields");
System.out.println(pStaticStr);
}
{
System.out.println("parent instance fields");
System.out.println(pStr);
}
public Parent(){
System.out.println("parent instance initialization");
}
}
public class Sub extends Parent{
public static String sStaticStr = "sub static string";
public String sStr = "subinstance string";
static{
System.out.println("substatic fields");
System.out.println(sStaticStr);
}
{
System.out.println("sub instance fields");
System.out.println(sStr);
}
public Sub(){
System.out.println("sub instance initialization");
}
public static void main(String[] args){
System.out.println("sub main");
new Sub();
}
}
在运行Sub中的main函数最终的结果如下:
过程如下,在执行main函数时,将要先对Sub进行类初始化,此时应该先对Sub的父类Parent进行类初始化,因此执行Parent的静态代码块,输出
parent static fields
parent static string
随后,进行Sub的类初始化,执行Sub的静态代码块:
sub static fields
sub static string
然后main函数开始执行,输出
sub main
由于在执行main函数的时候,Parent和Sub已经完成了类的初始化,因此,在调用 new Sub()只需要完成实例的初始化:
实例的初始化同样遵循先初始化父类的顺序。
先初始化父类的变量字段和代码块,因此输出
parent instance fields
parent instance string
随后调用父类的构造函数,输出:
parent instance initialization
父类实例初始化完成,随后子类同样按照先变量字段和代码块后构造函数的顺序初始化。
同样的道理,如果将Sub类的main函数去掉,改为在另一个Test类中调用new Sub,如下:
public class Test{
public static void main(String[]args){
System.out.println("main");
newSub();
System.out.println("\n");
newSub();
}
}
最后的输出结果将是:
总结:整个new的过程,如果类已经被初始化,则直接初始化实例,按照父类实例变量和代码块->父类构造器->子类实例变量和代码块->子类构造器的顺序执行。如果类还没有被初始化,应先初始化类,再初始化实例。初始化类的时候同样应先初始化父类,顺序要按照代码中的顺序。