Java 初始化

类初始化和实例化

  • 三种机制:声明赋值,初始化块,构造函数
  • 两种类型:静态,非静态
  • 一种关系:父子

触发场景


类初始化触发条件

  • 静态方法调用
  • 非常量静态域访问或赋值
  • 动态类加载Class.forName
  • 实例化对象前如果尚未加载

获取对象的常规方式,类实例初始化触发条件

  • new关键字生成新实例
  • 反射newInstance

获取实例的特殊方式,底层直接二进制组装,不会执行构造

  • 反序列化
  • clone

机制间顺序


类初始化过程:声明赋值(静态) <-> 初始化块(静态)

Sample
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
class Field {
public Field(String str) {
System.out.println(str);
}
}

class Clazz {
public static Field field1 = new Field("static member");
static {
System.out.println("static block");
}

public static Field field2 = new Field("static member");
public static void method() {

}
}

public class Test {
public static void main(String[] args) {
Clazz.method();
// static member
// static block
// static member
}
}

类实例化过程:声明赋值 <-> 初始化块 -> 构造函数

Sample
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
class Field {
public Field(String str) {
System.out.println(str);
}
}

class Clazz {
public Clazz() {
System.out.println("constructor");
}

private Field field1 = new Field("member");

{
System.out.println("block");
}

private Field field2 = new Field("member");

}

public class Test {
public static void main(String[] args) {
new Clazz();
// member
// block
// member
// constructor
}
}

总结

  • 不管是否静态,声明赋值和初始化块取决于书写顺序
  • 实例构造过程,构造函数最后被执行

类型间顺序


静态 > 非静态

先类加载才能实例化,静态过程只在类加载时执行,实例过程可以执行多次,每个实例独立
特殊过程,静态成员实例化自己,在静态初始化过程中穿插实例化

Sample
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
public class Test {
public static void main(String[] args) {
//1. 触发类加载
staticFunction();
}

//2. 静态变量初始化,实例化初始化,进入实例化流程
static Test st = new Test();

//6. 上面一行执行完,轮到
static {
System.out.println("1");
}

//3. 实例初始化块
{
System.out.println("2");
}

//5. 实例化最后构造函数,此时静态b还没轮到初始化值为0
Test() {
System.out.println("3");
System.out.println("a=" + a + ",b=" + b);
}

public static void staticFunction() {
//8. 加载完成,执行方法
System.out.println("4");
}

//4. 实例变量初始化
int a = 5;
//7. 上面静态块执行完,轮到
static int b = 6;
}

父子关系


子类对象实例化过程

  1. 先加载父并静态初始化
  2. 再加载子类并静态初始化
  3. 然后构建父类
  4. 最后构建子类
Sample
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
class Field {
public Field(String str) {
System.out.println(str);
}
}

class Parent {
public static Field field1 = new Field("parent static member");
private Field field2 = new Field("parent member");

static {
System.out.println("parent static block");
}
{
System.out.println("parent block");
}

public Parent() {
System.out.println("parent constructor");
}
}

class Child extends Parent {
public static Field field1 = new Field("child static member");
private Field field2 = new Field("child member");

static {
System.out.println("child static block");
}
{
System.out.println("child block");
}

public Child() {
System.out.println("child constructor");
}
}

public class Test {
public static void main(String[] args) {
new Child();
// parent static member
// parent static block
// child static member
// child static block
// parent member
// parent block
// parent constructor
// child member
// child block
// child constructor
}
}

不会触发初始化的情况


  • 成员变量的类不会跟随加载,只有真的存在对象实例才会加载
    比如A类包含B类成员变量,A实例化后被加载,但是B不会一起加载

  • 类加载器可以接受参数,不执行初始化阶段

Sample
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
class Field {
public Field(String str) {
System.out.println(str);
}
}

class Clazz {
public static Field field1 = new Field("static member");
private Field field2 = new Field("member");
static {
System.out.println("static block");
}
{
System.out.println("block");
}

public Clazz() {
System.out.println("constructor");
}
}

public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("test.Clazz", false, Test.class.getClassLoader());
//
}
}
  • 访问静态常量,不会初始化
    编译时常量(基本类型或字面量字符串,以及它们的运算符表达式),编译过程中就会发生常量折叠,变量被直接替换成值

程序使用第三方包的常量,如果第三方修改了常量值,直接更换新版依赖,结果还是旧值,这是因为编译后直接被替换成值,不会再去包内读取,感知不到变化,程序代码必须重新编译才行

Sample
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
class Field {
public Field(String str) {
System.out.println(str);
}
}

class Parent {
public static final int field = 0;
public static Field member = new Field("parent static member");
static {
System.out.println("static block parent");
}
}

class Child extends Parent {
public static final int field = 1;
public static Field member = new Field("child static member");
static {
System.out.println("static block child");
}
}

public class Test {
public static void main(String[] args) {
System.out.println(Child.field);
System.out.println(Parent.field);
// 1
// 0
}
}
  • 通过子类访问父类静态变量,并不会初始化子类,本质上和子类无关
Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Parent {
public static int field = 0;
static {
System.out.println("static block parent");
}
}

class Child extends Parent {
static {
System.out.println("static block child");
Parent.field = 1;
}
}

public class Test {
public static void main(String[] args) {
System.out.println(Child.field);
// static block parent
// 0
}
}

构造函数事项


  • 构造函数中不应调用可被覆写的方法
    原本父类调用自身方法进行初始化,但是子类复写后,变成调用子类的方法,有可能使父类行为异常
  • 构造函数间可以重载相互调用