再谈静态方法与静态变量的加载

无论是否是静态的,Java对类的加载都是惰性的,即只有要用到某个类的静态变量或方法时才去加载它,static初始化是在类加载时进行的

看下面的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package demo;

class demo1{
static int i;
static {
i= value();
System.out.println("加载demo1静态块");
}

private static int value(){
return 12;
}

}
public class demo {
public static void main(String[] args){
System.out.println("进入main函数");
}
}

输出

1
进入main函数

可知,由于demo入口函数main中未用到类demo1,因此,即使demo1中有静态变量或静态方法,也不加载


再看下面一个实例

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
package demo;

class demo1{
static {
System.out.println("初始化静态代码块");
}

{
System.out.println("初始化普通代码块");
}

public demo1() {
// TODO Auto-generated constructor stub
System.out.println("初始化构造器");
}
public static void main(String[] args){
System.out.println("进入main函数-------------demo1");
}

}

public class demo {

static {
System.out.println("初始化静态代码块");
}

{
System.out.println("初始化普通代码块");
}

public demo() {
// TODO Auto-generated constructor stub
System.out.println("初始化构造器");
}

public static void main(String[] args){
System.out.println("进入main函数-----------demo");
new demo1();
}
}

1
2
3
4
5
初始化静态代码块
进入main函数-----------demo
初始化静态代码块
初始化普通代码块
初始化构造器

先谈谈main入口函数的作用

  • 每个类都可以有main入口函数,但是只有public类才能执行这个入口函数

对于非public类的初始化?

1
2
3
初始化静态代码块
初始化普通代码块
初始化构造器

由上面结果可以看出

  • 优先初始化静态代码块
  • 再初始化普通代码块
  • 最后初始化构造器(构造器也是静态方法)

为什么会这样?
我的理解是:

  • 初始化一个类,之所以要先初始化一个静态代码块和普通代码块,因为初始化构造器时可能要用到这些值(一切以安全为前提)
  • 在初始化构造器时,如果有基类,则要先初始化基类,这样当所有的依赖关系都初始化好后,再初始化构造器的后续代码

对于public类的初始化

初始化public类还需考虑到main函数

参考上面例子中的结果

1
2
3
4
5
初始化静态代码块
进入main函数-----------demo
初始化静态代码块
初始化普通代码块
初始化构造器

demo类的main函数代码

1
2
3
4
5
public static void main(String[] args){
System.out.println("进入main函数-----------demo");
//新建一个demo1实例,使Java初始化demo1类
new demo1();
}

  • 从上面结果中可以看出,虽然有执行main函数,但是并没有完整的初始化demo类,没有执行demo类的构造器普通代码块

  • 我的理解是,Java奉行的是能不初始化就不初始化的原则,因此java虚拟机加载public demo类时,会优先将静态代码块初始化,然后跳过构造器,直接进入main函数,按需初始化

  • 由于main函数中并未找到需要完整初始化demo类的请求,因此就出现上面的结果

下面看一下改进版,如果在main函数中新建一个demo实例会怎样

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
package demo;

class demo1{
static {
System.out.println("初始化静态代码块");
}

{
System.out.println("初始化普通代码块");
}

public demo1() {
// TODO Auto-generated constructor stub
System.out.println("初始化构造器");
}
public static void main(String[] args){
System.out.println("进入main函数-------------demo1");
}

}

public class demo {

{
System.out.println("初始化普通代码块-----------demo");
}

static {
System.out.println("初始化静态代码块-----------demo");
}



public demo() {
// TODO Auto-generated constructor stub
System.out.println("初始化构造器-----------demo");
}

public static void main(String[] args){
System.out.println("进入main函数-----------demo");
new demo1();
new demo();


}

}

结果

1
2
3
4
5
6
7
初始化静态代码块-----------demo    //a
进入main函数-----------demo //b
初始化静态代码块 //c
初始化普通代码块 //d
初始化构造器 //e
初始化普通代码块-----------demo //f
初始化构造器-----------demo //g

从中可以看出

  • a是进入demo类main函数前的工作 –> 初始化静态域
  • c~e是执行new demo1()代码 –> 初始化demo1类(非public类)
  • f~g是执行new demo()代码 –> 初始化demo类(由于静态域已经初始化过,因此就无需再初始化一次)

静态变量/方法的向前引用

再看下面的例子

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
package demo;

class demo1{

/*
* A
*/
static {
i= value();
System.out.println("加载demo1静态块");
}
/*
* B
*/
static int i;

/*
* C
*/

private static int value(){
return 12;
}

}

public class demo {
public static void main(String[] args){
System.out.println("进入main函数");
int i = demo1.i;
System.out.println(i);
}

}

输出

1
2
3
进入main函数
加载demo1静态块
12

  • 代码中ABC三部分顺序可以互换,不影响结果,这也称为向上引用
  • 我的理解是由于静态变量/方法/域初始化在编译期完成,因此顺序不重要,Java会完整遍历所有静态变量/方法/域,因此顺序不重要

本文标题:再谈静态方法与静态变量的加载

文章作者:定。

发布时间:2017年8月1日 - 11时08分

本文字数:4,207字

原始链接:http://cocofe.cn/2017/08/01/再谈静态方法与静态变量的加载/

许可协议: Attribution-NonCommercial 4.0

转载请保留以上信息。