1 基础知识
Java类中常见的普通变量调用顺序比较简单,遇到静态常量以及构造快、静态块时就容易对变量的调用顺序产生迷惑,今天看到一个笔试题目,顺便写一下自己学习的心得。
1.1 静态变量与非静态变量
Java类中的变量分为静态变量和非静态变量,静态变量属于类的范畴,与实例无关,而非静态变量是与实例息息相关的,只有在实例化时非静态变量才会进行调用,因此静态变量的调用顺序要早于非静态变量的。
1.2 代码块与静态块
Java类中的代码块是指用一对儿大括号括起来的代码,比如下面的VarOrder类中就有一个代码块用于输出“代码块”三个字:
class VarOrder{
//我是代码块
{
System.out.println("代码块");
}
}
Java类中的静态块与代码块类似,只是前面加了static修饰符,比如下面的VarOrder类中除了代码块之外还有一个静态块用于输出“静态块”三个字:
class VarOrder{
//我是代码块
{
System.out.println("代码块");
}
//我是静态块
static{
System.out.println("静态块");
}
}
要理解代码块与静态块的调用顺序,其实也非常简单,带static的是与类相关的,与静态常量相似,只要类加载就会调用,而代码块与非静态常量相似,是与实例相关的,只有在实例化时才调用代码块的内容。
1.3 代码块与构造函数
Java类中一般都有自己的构造函数,那在进行实例化时代码块和构造函数又是谁先谁后呢?
其实可以这样理解,类进行实例化时需要先调用类的变量,否则无法使用类的变量就无法为类的变量赋值,因此在构造函数实例化之前,会先调用类的变量以及类的代码块,然后才进入到类的构造函数中进行实例化。
1.4 总结
当一个类中存在静态变量、非静态变量、代码块、静态块和构造函数时,他们的调用顺序根据前面的分析可以表示为:
静态(变量、块)>非静态(变量、代码块)>构造函数
也就是说:
第一梯队是静态变量和静态块,跟类的实例没有关系的,只要类加载就会先运行,如果同时存在,谁写在类的前面谁先调用;
第二梯队是非静态变量和代码块,这个要到初始化变量的时候才会调用,很好理解,变量是实例的变量,没有实例是不会调用普通变量的,普通变量和代码块的执行顺序也是按定义的前后执行;
第三梯队是构造函数,前面的调用完毕了才轮到他。
2 实例解析
下面通过网上的一个笔试题目类进行解析说明。
2.1 静态变量与非静态变量
public class VarOrder {
public static int k = 0;
public static int i = print("i");
public static int n = 99;
private int a = 0;
public int j = print("j");
public VarOrder(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
VarOrder t = new VarOrder("init");
}
}
上面的例子中有静态变量、非静态变量和构造函数,根据分析,静态变量先进行调用,因此会先输出i,然后是实例变量j进行赋值,输出j,最后是构造函数,输出init。输出结果为:
1:i i=0 n=0
2:j i=1 n=99
3:init i=2 n=100
当然,在main函数中也可以什么都不写,因为没有实例化对象,因此j的赋值语句不再调用,从而输出的结果如下:
1:i i=0 n=0
2.2 代码块与静态块
public class VarOrder {
public static int k = 0;
public static int i = print("i");
public static int n = 99;
private int a = 0;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public VarOrder(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
VarOrder t = new VarOrder("init");
}
}
根据上述分析,静态块与静态变量相似,构造块与非静态变量相似,因此首先会调用静态变量int k(没有输出),然后int i,然后是int n(没有输出),接着是静态块,静态调用完毕后调用实例变量int a, int j和代码块,最后才是构造函数。因此输出的结果如下:
1:i i=0 n=0
2:静态块 i=1 n=99
3:j i=2 n=100
4:构造块 i=3 n=101
5:init i=4 n=102
同样的道理,如果上述main方法中没有代码,输出的结果将只包含静态变量和静态块内容,输出结果如下:
1:i i=0 n=0
2:静态块 i=1 n=99
2.3 实例化对象作为静态变量时–终极笔试题
下面的代码是网友提供的笔试题目,只是将类的名字做了一下修改:
public class VarOrder {
public static int k = 0;
public static VarOrder t1 = new VarOrder("t1");
public static VarOrder t2 = new VarOrder("t2");
public static int i = print("i");
public static int n = 99;
private int a = 0;
public int j = print("j");
{
print("构造块");
}
static {
print("静态块");
}
public VarOrder(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++i;
++n;
}
public static int print(String str) {
System.out.println((++k) + ":" + str + " i=" + i + " n=" + n);
++n;
return ++i;
}
public static void main(String args[]) {
VarOrder t = new VarOrder("init");
}
}
可以看到,在类的静态变量中有两个该类的实例对象t1和t2,这就稍微增加了分析的难度,尤其里面涉及到++操作,不过也不用怕,我们坚持前面的理论分析一步一步分析即可。
1). 静态的一定是最开始进行调用的,如果存在多个,那么写在前面的先调用,因此静态变量int k先调用,没有输出;
2). 静态变量t1开始调用,t1是该类的实例对象,因此在实例对象中,非静态变量和代码块要首先进行初始化,因此int a和int j先进行调用进行赋值,然后是代码块进行调用。虽然类的静态变量和静态代码块的调用顺序要高于非静态变量和代码块,但是因为这里的t1是实例对象,因此不会跳转到t1和t2后面的静态变量int i和int n中执行赋值,打印输出的都是i和n的初始值,从0开始。最后才是自身的构造函数进行调用,输出如下:
1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
同理t1调用后,t2进行调用,输出的内容与t1实例化时相似,如下:
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
3). t2实例化后,继续顺序执行,开始执行静态变量int i,此时输出内容:
7:i i=6 n=6
4). 继续进行静态变量int n赋值,没有输出,但是要记住此时n的值已经由6变为99;
5). 最后一个静态调用,即静态块调用,此时输出如下:
8:静态块 i=7 n=99
6). 静态变量和静态块调用完毕后,此时才开始进入到主方法的代码中执行,主方法中的代码就一句,实例化对象,与2)分析的一致,先调用非静态变量和代码块,最后调用构造函数,因此输出如下:
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102
综上所述,最终的输出结果为:
1:j i=0 n=0
2:构造块 i=1 n=1
3:t1 i=2 n=2
4:j i=3 n=3
5:构造块 i=4 n=4
6:t2 i=5 n=5
7:i i=6 n=6
8:静态块 i=7 n=99
9:j i=8 n=100
10:构造块 i=9 n=101
11:init i=10 n=102