先做个常见的面试题
class Parent {
static String TAG = "Victor";
static {
System.out.println("Parent静态代码块");
}
}
class Child extends Parent {
static {
System.out.println("Child静态代码块");
}
}
public static void main(String[] args) {
System.out.println(Child.TAG);
}
结果输出
Parent静态代码块
Victor
虽然写的代码是用子类调用父类的TAG
,但是只调用了父类的静态代码块
因为对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。
稍微做些改动
在Parent
的TAG
前面加个final
,其余不变
class Parent {
static final String TAG = "Victor";
static {
System.out.println("Parent静态代码块");
}
}
结果输出
Victor
改动完以后与父类也无关了
因为加了final
以后,TAG
变成了常量,常量在编译阶段会存到调用的类的常量池中,所以运行的时候 main 方法调用TAG
时已经和父类无关了,不会触发父类的初始化
再举一个例子
class Parent {
static {
System.out.println("Parent静态代码块");
}
{
System.out.println("Parent普通代码块");
}
Parent() {
System.out.println("Parent构造方法");
}
}
class Child extends Parent {
static {
System.out.println("Child静态代码块");
}
{
System.out.println("Child普通代码块");
}
Child() {
System.out.println("Child构造方法");
}
}
public static void main(String[] args) {
Parent child = new Child();
}
结束输出
Parent静态代码块
Child静态代码块
Parent普通代码块
Parent构造方法
Child普通代码块
Child构造方法
再加上变量的初始化
class Example {
private static Example example1 = new Example();
public static int num1 = 1;
public static int num2 = 2;
private Example() {
num1 = 100;
num2 = 200;
}
public static Example getInstance() {
return example1;
}
}
public static void main(String[] args) {
Example.getInstance();
System.out.println(Example.num1);
System.out.println(Example.num2);
}
结果输出
1
2
如果将private static Example example1 = new Example();
位置变换一下
class Example {
public static int num1 = 1;
public static int num2 = 2;
private static Example example1 = new Example();
private Example() {
num1 = 100;
num2 = 200;
}
public static Example getInstance() {
return example1;
}
}
public static void main(String[] args) {
Example.getInstance();
System.out.println(Example.num1);
System.out.println(Example.num2);
}
结果输出
100
200
两个结果却不一样的原因很简单,就是属性是按自上而下的顺序初始化的
例1里面执行private static Example example1 = new Example();
的时候,num1
和num2
还是原始的值0
,并没有执行到赋值的语句,然后构造函数内将num1
和num2
赋值以后new Example();
算是执行完成了,接下来继续往下走,将num1
赋成1
,将num2
赋成2
例2就很容易理解了,执行private static Example example1 = new Example();
之前,num1
和num2
就已经初始化了,后来在构造函数里更改了
同一类里的变量和代码块顺序
class Parent {
public static int a = 100;
static {
System.out.println(a);
}
}
public static void main(String[] args) {
new Parent();
}
结果输出
100
如果调换顺序之后
class Parent {
static {
try {
val f = Parent.class.getDeclaredField("a");
System.out.println(f.get(null));
} catch (Exception ignored) {
}
}
public static int a = 100;
}
public static void main(String[] args) {
new Parent();
}
结果输出
0
说明执行 static 代码块的时候a
还没有赋值,说明执行顺序是由上到下,同理普通代码块也是一样
结论
- 父类静态变量、父类静态代码块 自上而下
- 子类静态变量、子类静态代码块 自上而下
- 父类普通变量、父类普通代码块 自上而下
- 父类构造方法
- 子类普通变量、子类普通代码块 自上而下
- 子类构造方法