今天女朋友给看了段代码,让我猜一下运行结果,表示很懵逼。
直接上代码!
public class StaticTest {
public static void main(String[] args)
{
staticFunction();
// System.out.println("main");
}
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
分析:
类的加载过程如下:
只有在准备阶段和初始化阶段才会涉及到类变量的初始化和赋值,所以这里只分析这两个过程。
准备阶段:
准备阶段是正式为类变量分配内存,并为类变量分配初始值。这些类变量所使用的内存都是在方法区上分配的。此时分配的只是类变量(被Static修饰的变量),而不包括实例变量。实例变量将会在类的实例化时创建和分配。而准备阶段的分配初始值“通常情况下”是数据类型的零值,如上面的代码:
static int b =112;
在准备阶段,b只分配了内存,并为执行赋值操作,所以b=0;因为,这个时候并未执行任何Java方法。而把value赋值为112的putstatic指令是程序被编译后,封装在构造函数中的,所以对b的赋值,是在类的初始化阶段才会执行!
所以准备阶段为 st 和 b 各自分配了内存。但是两个都没有值,st=null,b=0;
前面说的是通常情况下,那么特殊情况下就会不一样!
static final int b=112;
那么在准备阶段b的值就是112,而不再是0了。
初始化阶段:
类初始化阶段是类加载过程的最后一步,到了初始化阶段,才真正开始执行类中定义的java程序代码。在准备阶段,变量已经付过一次系统要求的初始值,而在初始化阶段,则根据程序猿通过程序制定的主管计划去初始化类变量和其他资源,当然也是有顺序的,先初始化类变量和静态代码块,再初始化实例变量和实例代码块,最后执行构造函数。
或者说:初始化阶段是先执行类构造器()方法,然后执行实例构造器方法的过程.
()方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块static{}中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块可以赋值,但是不能访问。如下代码:
public class Test
{
static
{
i=0;
System.out.println(i);//这句编译器会报错:Cannot reference a
//field before it is defined(非法向前应用)
}
static int i=1;
}
此处注意:正好有篇博客就在此处写错了,
竟然写成了:先给i赋值,再输出!(最后面贴出了这篇文章)
所以大家也要注意,编译器收集的顺序是由语句在源文件中出现的顺序所决定的!
总结流程!
1. 首先在执行此段代码时,首先由main方法的调用触发静态初始化。
2. 在初始化Test 类的静态部分时,遇到st这个成员
3. 此时还是准备阶段,所以并没有为st成员new对象,而是给st分配了内存,并指向null,并且为b分配内存赋值0
4. 开始初始化实例,此时执行Java代码,去初始化类变量和其他的资源。先实例化静态变量和静态代码块。但是静态代码块中的这条语句:
static StaticTest st = new StaticTest();
在静态初始化的过程中遇到了new StaticTest();
所以,开始进行实例变量初始化,静态初始化过程还没完成,就要初始化实例部分了。所以此时就开始进行实例初始化部分了!(注意:一旦开始初始化静态部分,无论是否完成,后续都不会再重新触发静态初始化流程了。)
注意:此时静态初始化的结果是st=null,b=0;
5.上一步静态初始化过程中冒出个实例变量初始化,所以此时的初始化就是实例变量和实例代码块的初始化,最后再执行构造函数!(只有后面再次遇到static,才会再次触发发静态初始化流程)
此时的实例初始化先遇到的是:实例代码块
{
System.out.println("2");
}
所以第一个输出的结果是2.
然后实例化int a=110;
6. 经过上一步初始化完实例代码块和实例变量之后,开始进入main函数执行构造方法。此时遇到了main函数的static。会在构造函数执行完之后,继续执行静态资源的初始化,也就是说现在的b还没初始化,仍然是0。所以现在执行的代码是:
StaticTest()
{
System.out.println("3");
System.out.println("a="+a+",b="+b);
}
这一步输出的结果是:
3
a=110,b=0
7.上一步构造方法执行完之后,开始进行静态资源的实例化。此时先遇到下面的代码块:
static
{
System.out.println("1");
}
然后是实例化b:static int b =112;
所以这一步结束之后输出:1
同时b被初始化为112
8.执行main方法中的 staticFunction();并输出:4
综上步骤所得结果顺序是:
2
3
a=110,b=0
1
4
为了验证第7步中b被实例化为112了,可以在staticFunction();方法中输出b来验证。再次就不验证了!
下面这篇文章内容就是前面提到错误的文章!
参考文章:Java虚拟机类加载机制——案例分析