Java中变量的初始化顺序

文章目录
  1. Java中变量的初始化顺序
    1. I. 初始化顺序
    2. II. 静态变量初始化顺序
    3. III. 成员变量的初始化
    4. IV. 小结
      1. 1. 初始化顺序
      2. 2. 初始化异常时
    5. V. 其他
      1. 声明
      2. 扫描关注,java分享

Java中变量的初始化顺序

在写一个通用的报警模块时,遇到一个有意思的问题,在调用静态方法时,发现静态方法内部对静态变量引用时,居然抛出了npe,仿佛是因为这个静态变量的初始化在静态方法被调用时,还没有触发,从而导致这个问题,因此今天专门来学习下静态成员的初始化顺序,以及上面这个问题导致的原因

I. 初始化顺序

类的初始化顺序

静态变量, 静态代码快 -》 实例变量(属性,实例代码块,构造方法)

继承关系初始化顺序

父类静态成员,静态代码块 -》 子类静态成员,静态代码块 -》 父类实例变量(属性,实例代码块,构造方法)-》子类实例变量(属性,实例代码块,构造方法)

II. 静态变量初始化顺序

类初始化时,会优先初始化静态成员,那么一个类中有多个静态成员时,如何处理的?

下面是一个使用静态成员,静态代码块,静态方法的测试类,那么下面的输出应该是怎样的呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class StaticTest {
static class A {
public A(int i) {
System.out.println("a init! " + i);
}
}
static class B {
public B(int i) {
System.out.println("b init! " + i);
}
}

private static A a1 = new A(1);
private static B b1;

private static int num;

private static B b2 = new B(2);
private static A a2 = genA(2);

static {
b1 = new B(1);
}

private static A genA(int i) {
System.out.println("gen A: " + i);
return new A(i);
}

private static B genB(int i) {
System.out.println("gen B: " + i);
return new B(i);
}

public static void doSome() {
System.out.println("static function doSome called! a3!=null : " + (a3 != null) + " | num > 0 : " + num);
}

private static A a3;
private static B b3;

static {
System.out.println("num : " + num);
num = 10;
a3 = genA(3);
b3 = genB(3);
}

public static void main(String[] args) {
doSome();
}
}

输出如下

1
2
3
4
5
6
7
8
9
10
11
a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
num : 0
gen A: 3
a init! 3
gen B: 3
b init! 3
static function doSome called! a3!=null : true | num > 0 : 10

从实际的输出结果来看:

  • 初始化的顺序比较清晰了,压根就是根据初始化代码的先后顺序来的,
  • 且在调用静态方法时,静态方法内部的静态成员已经被初始化

那么问题来了,如果在某个静态成员初始化的时候抛出了异常,会怎样?

那么稍稍改一下上面的代码,加一个主动抛异常的case

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class StaticTest {

static class A {
public A(int i) {
System.out.println("a init! " + i);
}
}

static class B {
public B(int i) {
System.out.println("b init! " + i);
}
}


private static A a1 = new A(1);
private static B b1;

private static int num;

private static B b2 = new B(2);
private static A a2 = genA(2);

static {
b1 = new B(1);
}

private static A genA(int i) {
System.out.println("gen A: " + i);
return new A(i);
}

private static B genB(int i) {
System.out.println("gen B: " + i);
return new B(i);
}

private static A aError = genError();
private static A genError() {
System.out.println("gen error!");
throw new RuntimeException();
// return new A(10);
}

public static void doSome() {
System.out.println("static function doSome called! a3!=null : " + (a3 != null) + " | num > 0 : " + num);
}

private static A a3;
private static B b3;

static {
System.out.println("num : " + num);
num = 10;
a3 = genA(3);
b3 = genB(3);
}




public static void main(String[] args) {
doSome();
}
}

此时输出:

1
2
3
4
5
6
7
8
a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
gen error!
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException

也就是说,初始化异常之后的代码将不会在继续执行

那么第二个问题来了,前面说到哪个问题是什么情况

最开始说到,在调用类的静态方法时,发现本该被初始化的静态成员,依然是null,从上面的分析来说,唯一的可能就是在成员变量初始化的过程中,出现了异常

那么,就有另一个问题了,初始化就报错了,这个类的静态方法还能被调用执行么(加入这个静态方法不依赖内部的静态成员)?

将前面的 genA()方法的private去掉,改成默认的访问范围,然后下面给出一个演示:

post.gif

通过这个演示,也挺有意思的,第一次访问,会抛出一个初始化异常;但是再调用一次,结果发现居然正常执行了;但是调用public方法时,每次都是抛异常

导致这个问题的原因,还有待考究,但是前面这个问题的答案,估摸着和下面差不多了(但是不敢确定,有待大神指点)

  • 理论上类初始化失败,应该就不允许被调用了
  • 但是某些情况下,可以绕过这个限制

III. 成员变量的初始化

测试case也比较简单,把前面的代码中的static去掉即可, 输出

1
2
3
4
5
6
7
8
9
10
11
a init! 1
b init! 2
gen A: 2
a init! 2
b init! 1
num : 0
gen A: 3
a init! 3
gen B: 3
b init! 3
static function doSome called! a3!=null : true | num > 0 : 10

依然是根据初始化代码的先后顺序进行的

当然如果出现异常的情况,和前面的结果类似,不再赘述

IV. 小结

1. 初始化顺序

类的初始化顺序

静态变量, 静态代码快 -》 实例变量(属性,实例代码块,构造方法)

继承关系初始化顺序

父类静态成员,静态代码块 -》 子类静态成员,静态代码块 -》 父类实例变量(属性,实例代码块,构造方法)-》子类实例变量(属性,实例代码块,构造方法)

相同等级的初始化的先后顺序,是直接依赖代码中初始化的先后顺序

2. 初始化异常时

理论上,类初始化中抛出了异常,那么这个类将无法被classLoader正确的加载,因此也无法有效的使用这个类

但是不排除某些情况下,依然强行的使用了这个类(如上面gif图中的演示),这个原理还不太清晰,也有可能是idea的debug功能有什么黑科技?

注意

因此,请格外注意,在初始化代码中,请确保不会有抛出异常,如果无法把控,不妨新建一个init()方法来实现初始化各种状态,然后在代码中主动调用好了

V. 其他

声明

尽信书则不如,已上内容,纯属一家之言,因本人能力一般,见解不全,如有问题,欢迎批评指正

扫描关注,java分享

QrCode

# JDK, Java

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×