0%

Java编程思想#初始化与清理

本文在“针对类(Class)和抽象类(Abstract Class)(即不包括接口(Interface),枚举(Enum),注解(Annotation)),且不存在继承关系”的背景下进行介绍。

一、初始化

1.1、实例对象和类对象初始化

1.1.1、实例对象初始化

实例对象的字段(即普通字段)按照如下顺序进行初始化:

1
2
3
4
1、默认初始化
2、定义初始化
3、实例初始化语句初始化
4、构造器初始化

示例代码如下:

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

public class InitialOrder {

//默认初始化和定义初始化
int i = f(10);

{
System.out.println("定义初始化后 i:" + i);

//实例初始化语句初始化
i = 20;

System.out.println("实例初始化语句初始化后 i:" + i);
}

public InitialOrder() {
//构造器初始化
i = 30;

System.out.println("构造器初始化后 i:" + i);
}

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

public int f(int a) {
System.out.println("默认初始化后 i:" + i);

return 10;
}
}

触发条件:实例对象的创建。需要注意的是:1)实例对象初始化中“默认初始化”和“构造器初始化”初始化阶段必存在,“定义初始化”和“实例初始化语句初始化”初始化阶段可选存在;2)实例对象初始化中初始化阶段之间连续不间断。

1.1.2、类对象初始化

类对象的字段(即静态字段)按照如下顺序进行初始化:

1
2
3
1、默认初始化
2、定义初始化
3、静态初始化语句初始化

示例代码如下:

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

public class InitialOrder2 {

public static void main(String[] args) throws ClassNotFoundException {
//如果第一次遇见ExperimentObject类的声明,创建类对象,且触发“默认初始化”
ExperimentObject object = null;

//如果ExperimentObject类对象已经创建,则触发“定义初始化和静态初始化语句初始化”;否则创建ExperimentObject类对象,且触发“默认初始化,定义初始化,静态初始化语句初始化”
Class.forName("chapter5.ExperimentObject");
}
}

class ExperimentObject {

//默认初始化和定义初始化
static int i = f(10);

static {
System.out.println("定义初始化后 i:" + i);

//静态初始化语句初始化
i = 20;

System.out.println("静态初始化语句初始化后 i:" + i);
}

public static int f(int a) {
System.out.println("默认初始化后 i:" + i);

return 10;
}
}

类对象初始化中“默认初始化”初始化阶段触发条件:第一次遇见类的声明,从而引发加载类的Class文件和创建类对象。
类对象初始化中“定义初始化和静态初始化语句初始化”初始化阶段触发条件:第一次访问类的“静态字段,静态方法,构造方法”,第一次子类触发级联触发父类,Class.forName(相应类类路径)语句第一次执行。
综合来看:1)类对象初始化中“默认初始化”初始化阶段必存在,“定义初始化”和“静态初始化语句初始化”初始化阶段可选存在;2)类对象初始化中“默认初始化”和“定义初始化,静态初始化语句初始化”初始化阶段之间可间断不连续(因为“默认初始化”初始化阶段和“定义初始化,静态初始化语句初始化”初始化阶段有相互独立的触发条件),“定义初始化”和“静态初始化语句初始化”初始化阶段之间连续不间断。

1.2、其他

关于实例对象和类对象初始化有以下几点说明:

  • 实例对象和类对象初始化中“默认初始化”初始化阶段的默认初始化值遵照Java编程思想#一切都是对象中的表2
  • 类对象初始化先于实例对象初始化
  • 类对象初始化至多进行1次,实例对象初始化可进行任意多次

最后是一个关于实例对象和类对象初始化的综合示例:

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* 第一次访问Main类的静态方法,使得加载Main类的Class文件,创建类对象,引发类对象初始化中的“默认初始化”,接着引发类对象初始化中的“定义初始化和静态初始化语句初始化”<br/>
* 1)分配完类对象的内存之后,首先进行静态字段的“默认初始化”。静态字段a,b,c被默认初始化为0值<br/>
* 2)接下来进行静态字段的“定义初始化”。按照静态字段的定义顺序,依次进行静态字段的定义初始化,a没有定义初始化,b有定义初始化被初始化为10,这同时说明此时c还未被定义初始化,还只是默认初始化得到的0值,c有定义初始化,被初始化为20<br/>
* 3)执行“静态初始化语句初始化”过程。a被赋值为10,c被赋值为30<br/>
*
* <br/>
* 创建Main类实例对象<br/>
* 1)分配完实例对象的内存之后,首先进行普通字段的“默认初始化”。普通字段d,f,g被默认初始化为0值<br/>
* 2)按照普通字段的定义顺序,依次进行普通字段的“定义初始化”。d没有定义初始化,f有定义初始化被初始化为10,这同时说明此时g还未被定义初始化,还只是默认初始化得到的0值,g有定义初始化,被初始化为20<br/>
* 3)执行“实例初始化语句初始化”过程。d被赋值为10,g被赋值为40<br/>
* 4)执行“构造器初始化”过程。d被赋值为20,g被赋值为30<br/>
*
*/
public class Main {
static int a;

static int b = f();

static int c = 20;

static int f() {
return c + 10;
}

static {
System.out.println("static initialize start");
System.out.println(a);
System.out.println(c);

a = 10;
c = 30;

System.out.println(a);
System.out.println(c);
System.out.println("static initialize end");
}


int d;

int f = h();

int g = 20;

int h() {
return g + 10;
}

{
System.out.println("initialize start");
System.out.println(d);
System.out.println(g);

d = 10;
g = 40;

System.out.println(d);
System.out.println(g);
System.out.println("initialize end");
}

public Main() {
d = 20;
g = 30;

System.out.println("constructor print start");
System.out.println(d);
System.out.println(g);
System.out.println("constructor print end");
}

/**
* 第一次访问Main类的静态方法,使得加载Main类的Class文件,创建类对象,引发类对象初始化中的“默认初始化”,接着引发类对象初始化中的“定义初始化和静态初始化语句初始化”
*/
public static void main(String[] args) throws ClassNotFoundException {
System.out.println(Main.a);
System.out.println(Main.b);
System.out.println(Main.c);

System.out.println("分割线-------------");

// 创建Main类的实例对象
Main main = new Main();
System.out.println(main.d);
System.out.println(main.f);
System.out.println(main.g);
}
}

二、清理

实例对象和类对象由垃圾回收器负责进行回收。

三、其他

3.1、方法重载

1、重载方法之间以参数类型列表进行区分,不能以返回值进行区分
2、调用重载方法时,最终会绑定到最契合传入实参的重载方法,示例代码如下:

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 chapter5;

public class Overloading {

public static void main(String[] args) {
Overloading o = new Overloading();

byte a = 10;
o.f(a);

o.g(o);

o.h(a);

o.j(o);
}

public void f(short a) {
System.out.println("f(short)");
}

public void g(Object a) {
System.out.println("g(Object)");
}

public void h(short a) {
System.out.println("h(short)");
}

public void h(byte a) {
System.out.println("h(byte)");
}

public void j(Object a) {
System.out.println("j(Object)");
}

public void j(Overloading a) {
System.out.println("j(Overloading)");
}
}

3.2、构造器

当且只当不显式指定构造器时,编译器才会自动创建一个默认构造器。
允许进行构造器方法重载。

3.3、this关键词

this关键词有两个用途:“指代本实例对象”和“在构造器中调用构造器”。示例代码如下:

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

public class ThisKeyword {

int a;
int b;

public ThisKeyword(int a, int b) {
this.a = a;
this.b = b;
}

public ThisKeyword(int a) {
this(a, 10);
}

public void setA(int A) {
this.a = A;
}

public ThisKeyword getThisInstance() {
return this;
}

}

使用this调用构造器有以下限制:

  • “this调用构造器”只能在构造器中使用
  • “this调用构造器”只能置于构造器最起始处
  • “this调用构造器”在同一个构造器中至多使用1次

3.4、finalize()方法

垃圾回收过程中,在回收对象之前至多会调用1次对象的finalize()方法,因此,该方法机制的设计初衷是我们可以覆盖实现自己的finalize()方法,在覆盖实现后的finalize()方法中做垃圾回收前的清理工作,但是基于以下两点原因,我们不应该使用finalize()方法机制来做垃圾回收前的清理工作:

  • 有可能不进入垃圾回收过程,比如通过System.exit()命令直接退出Java进程
  • 在垃圾回收过程中,回收对象之前至多会调用1次对象的finalize()方法,但也有可能不调用

3.5、内存泄漏

在Java中,也有可能出现内存泄漏的情形,比如在本地方法中分配的内存(例如C/C++语言实现的本地方法中使用malloc方法分配内存)。

3.6、数组初始化

数组初始化有3种形式,需要注意的是,第1种只能用在数组定义处。分别以“int[]”和“String[]”数组为例进行说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package chapter5;

public class ArrayInitialize {

public static void main(String[] args) {
//该种数组初始化形式只能用在数组定义处
int[] a = {1, 2, 3};

int[] aa = new int[]{1, 2, 3};
int[] aaa = new int[3];
aaa[0] = 1;
aaa[1] = 2;
aaa[2] = 3;

//只能用在数组定义处
String[] b = {"hello", "world"};

String[] bb = new String[]{"hello", "world"};
String[] bbb = new String[2];
bbb[0] = "hello";
bbb[1] = "world";
}
}

备注:
1、数组初始化后,数组元素的默认值遵从Java编程思想#一切都是对象中的表2

3.7、可变参数列表

自从JDK 1.5开始,Java引入了对可变参数列表的支持,可变参数列表本质上还是一个数组,因此适用于“foreach语法”,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package chapter5;

public class VarArg {

public void f(int... a) {
for (int aa : a) {
System.out.println(aa);
}
}

public void g(Object... b) {
for (Object bb : b) {
System.out.println(bb);
}
}
}

3.8、方法实现体中的变量不会进行默认初始化

方法实现体中的变量不会像类中的字段(包括普通和静态字段)一样进行默认初始化。编译过程会因为存在未被初始化的变量而失败。示例代码如下:

1
2
3
4
5
6
7
8
9
10
package chapter5;

public class MethodVariableInitialization {

void f() {
int b;
//编译出错,提示变量b未被初始化
System.out.println(b);
}
}

3.9、失效的“向前引用”###

根据Java编程思想#一切都是对象可知,Java解决了“向前引用”的问题,即在某处可使用在其后定义的资源。
但是现在有如下一段代码:

1
2
3
4
5
6
7
8
9
10
11
public class Main {

int a = f(c);

int c = 10;

int f(int n) {
return n;
}

}

int a = f(c);语句处,编译器报出Illegal forward reference(非法的向前引用)错误。这看起来是“向前引用”失效了。
其实这个“向前引用”失效跟初始化顺序相关,编译器认为你的本意是将“10”传给f()方法,如果上面这段代码编译通过的话,f()方法最后得到的传入值将是“0”(c的默认初始化值为0)。为了防止这种“surprise”的产生,编译器报出了如上错误。(如果将int a = f(c);语句和int c = 10;语句互换位置,编译器就不会报错)
又有以下一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Main {

int a = g();

int g() {
return c;
}

public static void main(String[] args) {
Main main = new Main();
System.out.println(main.a);
}

int c = 10;
}

该段代码编译能够通过,但是从严格意义上来,如果按照上述思路,那么int a = g();语句也是一个非法的向前引用。
综上,Java关于这个问题的语法糖真是不伦不类。

您的支持将鼓励我继续分享!