下面将就这个问题,通过一些例子与大家探讨。
看下面的代码:
class B
{
static int a = 0;
{
System.out.println("B.scope is running");
a = 10 ;
}
static
{
System.out.println("B.static scope is running");
a = 20;
}
public B()
{
System.out.println("B.Constructor is running");
}
public static void main(String arg[])
{
System.out.println(B.a);
System.out.println(B.a);
B b1 = new B();
B b2 = new B();
System.out.println(b1.a);
System.out.println(b2.a);
System.out.println(B.a);
}
}
我们在类B中定义了一个静态变量a,并分别用静态初始化和f非静态初始化的方法将其初始化为10 和 20
在main方法中我们先没有将实例化,两次打印出类成员a来,然后将a实例化两次,再两次打印出对象成员a来,最后再打印出一次类成员a,想想结果会是什么了?
B.static scope is running
20
20
B.scope is running
B.Constructor is running
B.scope is running
B.Constructor is running
10
10
10
这是我所得到的实验结果。
可以看到:当我们在使用B类时,JVM将B的定义装载,这是它调用了B的静态定义初始化 和 静态代码块 由于是静态的,尽管我们打印了两次,静态定义初始化 和 静态代码块都只执行了一次。然后打印出两次20,接着非静态代码块被执行了,而且是两次,这是在我们实例化的时候被执行的,接着是构造函数由于我们在非静态代码块中改变了a的值所以打出来的是10,最后一个打印也很有意思,我们看到同样的一句代码System.out.println(B.a);在前面打印的结果是20,而在后面却是10,怎么回事?这是由于a是static类型的,所类有实例以及类成员本身都只是共享一份a,不管在哪里被改变,其他地方的值都睡随之改变,这也是为什么我们要在不希望值被改变静态成员前加上final的原因,当然如果我们允许程序该变某个变量,我们在变量前加上static 的直接好处是可以让我们不管在程序的任何地方都可以得到与其他地方一致的值,而且是最新的。
我们也可以将这个类稍微修改一下看看
class B
{
static int a = 0;
{
System.out.println("B.scope is running");
a = 10 ;
}
static
{
System.out.println("B.static scope is running");
a = 20;
}
public B()
{
a = 30;
System.out.println("B.Constructor is running");
}
public static void main(String arg[])
{
System.out.println(B.a);
System.out.println(B.a);
B b1 = new B();
B b2 = new B();
System.out.println(b1.a);
System.out.println(b2.a);
System.out.println(B.a);
}
}
运行结果为:
B.static scope is running
20
20
B.scope is running
B.Constructor is running
B.scope is running
B.Constructor is running
30
30
30
可以看到由于构造函数是后于非静态代码块执行的,所以a的值被修改成30,同时打印类成员是也可得到30.
再来看看继承的情况:
class B
{
static int a = 0;
{
System.out.println("B.scope is running");
a = 10 ;
}
static
{
System.out.println("B.static scope is running");
a = 20;
}
public B()
{
a = 30;
System.out.println("B.Constructor is running");
}
/*
public static void main(String arg[])
{
System.out.println(B.a);
System.out.println(B.a);
B b1 = new B();
B b2 = new B();
System.out.println(b1.a);
System.out.println(b2.a);
System.out.println(B.a);
}
*/
}
class C extends B
{
static int c = 0;
static
{
c = 10;
System.out.println("C.static code is runnning and now c is " + c);
}
{
c = 20;
System.out.println("C.non-static code is runnning and now c is " + c);
}
public C()
{
c = 30;
System.out.println("C.constructor is running and now c is " + c);
}
public static void main(String args[])
{
System.out.println(C.c);
System.out.println(C.c);
C c1 = new C();
C c2 = new C();
System.out.println(c1.c);
System.out.println(c2.c);
System.out.println(C.c);
}
}
我们将B中的启动函数main注释掉了,然后让C来继承B,C和B有着类似的结构,在次不多费口舌,看看打印结果:
B.static scope is running
C.static code is runnning and now c is 10
10
10
B.scope is running
B.Constructor is running
C.non-static code is runnning and now c is 20
C.constructor is running and now c is 30
B.scope is running
B.Constructor is running
C.non-static code is runnning and now c is 20
C.constructor is running and now c is 30
30
30
30
在此描述一下c变化过程,虽然定义了两个对象c1,c2但其实只有一份共有的c,
c被装载时先被初始化为0,后来执行静态代码块时被初始化为10,接着执行c1的非静态代码快时被初始化为20,然后是c1的构造函数被出化为30,然后轮到c2的非静态代码块,这是又重新被初始化回20,然后c2的构造函数,变为30.
还可以看到载装载类时先要装载该类的基类,一直往前指导Object类。在构造函数书中也是要调用积累的构造函数的。
总结:深入理解一个类从被JVM装载开始,各种代码的执行顺序。
被JVM装载->先以后面描述的方式之后执行父类的相关代码->如果有静态初始化,先执行静态初始化,且只执行一次,以后即使有该类实例化,也不会再执行->如果有静态代码块(以与静态初始化一样的方式执行)->如果有new语句带来的实例化,先为成员变量分配空间,并绑定参数列表,隐式或显式执行super(),即父类的构造方法,->执行非静态代码块-〉执行本类的构造函数-〉其他代码