类的生命周期是:
加载-->验证-->准备-->解析-->初始化-->使用-->销毁。只有在准备阶段和初始化阶段才会涉及类变量的初始化和赋值。
类的准备阶段需要做为类变量分配内存并设置默认值,因此类变量为null或者0。如果类变量是final,编译时javac会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将被变量设置为指定的值,不再是0或者null。
一般情况顺序如下:
1.父类的静态变量赋值
2.子类的静态变量赋值
3.父类成员变量赋值和父类块赋值
4.父类构造函数赋值
5.子类成员变量赋值和子类块赋值
6.子类构造函数赋值
注:如果父类中调用的方法子类重写了,那么调用子类的方法。
编译器会自动收集类中的所有静态变量(类变量)和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是根据语句在java代码中的顺序决定的。收集完成之后,会编译成java类的 static{} 方法,值得说明的是,如果我们的java类中,没有显式声明static{}块,如果类中有静态变量,编译器会默认给我们生成一个static{}方法。
Java收集我们的实例变量赋值语句,合并后在构造函数中执行赋值语句。没有构造函数的,系统会默认给我们生成构造函数。
例子1:
class Father{
static{
System.out.println("父类静态代码块初始化" );
}
{
System.out.println("父类代码块初始化" );
}
private static String s=print();
public static String print(){
System.out.println("父类静态方法" );
return "父类静态成员变量的初始化" ;
}
public Father(){
System.out.println("父类无参构造函数初始化完成" );
show();
}
public void show(){
System.out.println("父类show()方法" );
}
}
class Son extends Father{
static{
System.out.println("子类静态代码块初始化" );
}
{
System.out.println("子类代码块初始化" );
}
private static int i=1;
private String s="子类私有成员变量" ;
public void show(){
System.out.println("子类show()方法:i=" +i);
}
public Son(){
System.out.println("子类构造函数初始化完成" );
show();
}
}
public class TestClassLoadSeq {
public static void main(String[] args){
new Son();
}
}
输出顺序:
父类静态代码块初始化
父类静态方法
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=1
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1
例子2:
class Father{
private static String s = print();
static{
System.out.println("父类静态代码块初始化" );
}
{
System.out.println("父类代码块初始化" );
}
public static String print(){
System.out.println("父类静态方法" );
return "父类静态成员变量的初始化" ;
}
public Father(){
System.out.println("父类无参构造函数初始化完成" );
show();
}
public void show(){
System.out.println("父类show()方法" );
}
}
class Son extends Father{
static{
System.out.println("子类静态代码块初始化" );
}
{
System.out.println("子类代码块初始化" );
}
private int i = 1;
private String s="子类私有成员变量" ;
public void show(){
System.out.println("子类show()方法:i=" +i);
}
public Son(){
System.out.println("子类构造函数初始化完成" );
System.out.println("子类成员变量初始化完成:s=" +s);
show();
}
}
public class TestClassLoadSeq {
public static void main(String[] args){
new Son();
}
}
输出顺序:
父类静态方法
父类静态代码块初始化
子类静态代码块初始化
父类代码块初始化
父类无参构造函数初始化完成
子类show()方法:i=0
子类代码块初始化
子类构造函数初始化完成
子类成员变量初始化完成:s=子类私有成员变量
子类show()方法:i=1
还有一种特殊情况如下:
例子3:
public class StaticTest{
public static void main(String[] args){
staticFunction();
}
static StaticTest st = new StaticTest();
static{
System.out.println("1");
}
{
System.out.println("2");
}
StaticTest(){
System.out.println("3");
System.out.println("a="+a+",b="+b);
}
public static void staticFunction(){
System.out.println("4");
}
int a = 110;
static int b = 112;
}
输出顺序:
2
3
a=110,b=0
1
4
解析:
实例初始化不一定要在类初始化结束以后才开始初始化。
1.首先由main方法的调用触发了静态初始化。
2.在初始化StaticTest这个类时,遇到了st这个成员变量。
3.st引用的是本类的实例变量。此时静态初始化过程还没完成就要初始化实例部分了。
4.从java的角度,一旦开始初始化静态部分,无论是否完成,后续都不会再重新触发静态初始化流程了。
5.即把实例初始化嵌入到静态初始化流程中,导致了实例初始化完全在静态初始化之前。所以b没有值,为0。
6.其它流程和之前的类似,不再赘述。