转:http://www.jb51.net/article/89198.htm
前言
在Java中,一个对象在可以被使用之前必须要被正确地初始化,这一点是Java规范规定的。
自动初始化(默认值)
一个类的所有基本数据成员都会得到初始化,运行下面的例子可以查看这些默认值:
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
|
class
Default{
boolean
t;
char
c;
byte
b;
short
s;
int
i;
long
l;
float
f;
double
d;
public
void
show() {
System.out.println(
"基本类型 初始化值\n"
+
"boolean<----->"
+ t +
"\n"
+
"char<----->"
+ c +
"\n"
+
"byte<----->"
+ b +
"\n"
+
"short<----->"
+ s +
"\n"
+
"int<----->"
+ i +
"\n"
+
"long<----->"
+ l +
"\n"
+
"float<----->"
+ f +
"\n"
+
"double<----->"
+ d +
"\n"
);
}
}
public
class
InitValue {
public
static
void
main(String[] args) {
Default d =
new
Default();
d.show();
}
}
|
【运行结果】:
基本类型 初始化值
boolean<----->false
char<----->
byte<----->0
short<----->0
int<----->0
long<----->0
float<----->0.0
double<----->0.0
其中,char类型的默认值为空(null)。
对于非基本数据类型而言,对象的句柄也会被初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
class
Person {
private
String name;
// setter
}
class
Default {
Person p;
public
void
show() {
System.out.println(
"Person<----->"
+ p);
}
}
public
class
InitValue {
public
static
void
main(String[] args) {
Default d =
new
Default();
d.show();
}
}
|
【运行结果】:
Person<----->null
可见,句柄初始化值为null。这就是说,如果没有为p指定初始化值就调用类似于p.setName
的方法,就会出现异常。
规定初始化
如果需要自己为变量赋一个初始值,可以在定义变量的同时赋值。
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
|
class
Default{
boolean
t =
true
;
char
c =
'A'
;
byte
b =
47
;
short
s =
0xff
;
int
i =
24
;
long
l =
999
;
float
f =
1
.2f;
double
d =
1.732
;
public
void
show() {
System.out.println(
"boolean<----->"
+ t +
"\n"
+
"char<----->"
+ c +
"\n"
+
"byte<----->"
+ b +
"\n"
+
"short<----->"
+ s +
"\n"
+
"int<----->"
+ i +
"\n"
+
"long<----->"
+ l +
"\n"
+
"float<----->"
+ f +
"\n"
+
"double<----->"
+ d +
"\n"
);
}
}
public
class
InitValue {
public
static
void
main(String[] args) {
Default d =
new
Default();
d.show();
}
}
|
甚至可以通过一个方法来进行初始化;
1
2
3
4
|
class
Person {
int
i = set();
//...
}
|
这些方法也可以使用自变量:
1
2
3
4
5
|
class
Person {
int
i;
int
j = set(i);
//...
}
|
构建器初始化
构建器进行初始化的优点是可以在运行期决定初始化值。例如:
1
2
3
4
5
6
|
class
Person {
int
age;
Person() {
age =
89
;
}
}
|
age首先会初始化为0,然后变成89。对于所有基本类型以及对象的句柄,这种情况都是成立的。
初始化顺序
在一个类里,初始化的顺序是由变量在类内的定义顺序决定的。即使变量定义大量遍布于方法定义的中间,那么变量仍然会在调用任何方法(包括构造函数)之前得到初始化。例如:
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
|
class
Pet {
Pet(
int
age) {
System.out.println(
"Pet("
+ age +
")"
);
}
}
class
Person {
Pet t1 =
new
Pet(
1
);
Person() {
System.out.println(
"---Person()---"
);
t3 =
new
Pet(
33
);
}
Pet t2 =
new
Pet(
2
);
void
show() {
System.out.println(
"show----running"
);
}
Pet t3 =
new
Pet(
3
);
}
public
class
OrderOfInitialization {
public
static
void
main(String[] args) {
Person p =
new
Person();
p.show();
}
}
|
【运行结果】:
Pet(1)
Pet(2)
Pet(3)
---Person()---
Pet(33)<br/>
show----running
上例中,虽然t1、t2、t3的定义遍布于类中,但是初始化的先后顺序是由t1、t2、t3的定义顺序决定的(自己动手调换t1、t2、t3看看结果),且初始化优先于构建器执行,当调用Person的构建器时,t3重新初始化。
静态数据的初始化
如果数据是静态的(static),同样的过程也会执行。若属于基本类型,而且未对其进行初始化,就会自动获得自己的标准基本类型初始值;若它是指向一个对象的句柄,除非创建一个对象同它连接起来,否则得到一个空值(null)。如果在定义时初始化,采取的方式与非静态值是不同的,这是因为static只有一个存储区域。例如:
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
|
class
Bowl {
Bowl(
int
marker) {
System.out.println(
"Bowl("
+ marker +
")"
);
}
void
f(
int
marker) {
System.out.println(
"f("
+ marker +
")"
);
}
}
class
Table {
static
Bowl b1 =
new
Bowl(
1
);
Table() {
System.out.println(
"Table()"
);
b2.f(
1
);
}
void
f2(
int
marker) {
System.out.println(
"f2("
+ marker +
")"
);
}
static
Bowl b2 =
new
Bowl(
2
);
}
class
Cupboard {
Bowl b3 =
new
Bowl(
3
);
static
Bowl b4 =
new
Bowl(
4
);
Cupboard() {
System.out.println(
"Cupboard()"
);
b4.f(
2
);
}
void
f3 (
int
marker) {
System.out.println(
"f3("
+ marker +
")"
);
}
static
Bowl b5 =
new
Bowl(
5
);
}
public
class
StaticInitialization {
public
static
void
main(String[] args) {
System.out.println(
"Creating new Cupboard() in main"
);
new
Cupboard();
System.out.println(
"Creating new Cupboard() in main"
);
new
Cupboard();
t2.f2(
1
);
t3.f3(
1
);
}
static
Table t2 =
new
Table();
static
Cupboard t3 =
new
Cupboard();
}
|
【运行结果】:
Bowl(1)
Bowl(2)
Table()
f(1)
Bowl(4)
Bowl(5)
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
Creating new Cupboard() in main
Bowl(3)
Cupboard()
f(2)
f2(1)
f3(1)
静态代码块
Java允许将其他static初始化工作划分到类内一个特殊的代码块中,这种代码块的形式为static关键字,后面跟着一个方法主体,称为静态代码块。静态代码块只有在第一次生成那个类的对象或首次访问属于那个类的static成员时执行。例如:
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
|
class
Person {
Person(
int
age) {
System.out.println(
"Person("
+ age +
")"
);
}
void
f(
int
age) {
System.out.println(
"f("
+ age +
")"
);
}
}
class
Persons {
static
Person p1;
static
Person p2;
static
{
p1 =
new
Person(
1
);
p2 =
new
Person(
2
);
}
Persons() {
System.out.println(
"Persons()"
);
}
}
public
class
ExplicitStatic {
public
static
void
main(String[] args) {
System.out.println(
"Inside main()"
);
Persons.p1.f(
18
);
//1
}
static
Persons x =
new
Persons();
//2
static
Persons y =
new
Persons();
//2
}
|
在标记为1的行内访问static对象p1的时候,或在行1被注释而行2未被注释是,用于Persons的static初始化模块就会运行。若1和2都被注释掉,则用于Persons的静态代码块不会执行。
静态属性和静态代码块执行的先后顺序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
class
Person {
Person(
int
age) {
System.out.println(
"Person("
+age+
")"
);
}
}
class
Persons {
static
Person p =
new
Person(
2
);
// 1
static
{
p =
new
Person(
3
);
}
static
Person p =
new
Person(
2
);
// 2
}
public
class
CompStaticInit {
public
static
void
main(String[] args) {
}
static
Persons x =
new
Persons();
}
|
根据注释1保留2,注释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
|
class
Animal {
Animal(
int
age) {
System.out.println(
"Animal("
+ age +
")"
);
}
void
f(
int
age) {
System.out.println(
"f("
+ age +
")"
);
}
}
public
class
NotStaticInit {
Animal a1;
Animal a2;
{
a1 =
new
Animal(
1
);
a2 =
new
Animal(
2
);
System.out.println(
"a1 & a2 initialized"
);
}
NotStaticInit() {
System.out.println(
"NotStaticInit"
);
}
public
static
void
main(String[] args) {
System.out.println(
"Inside main()"
);
NotStaticInit x =
new
NotStaticInit();
}
}
|
类似于静态代码块,匿名代码块与非静态属性的初始化顺序取决于编码顺序。
继承中的对象初始化过程
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
|
class
Insect {
int
i =
1
;
int
j;
Insect() {
prt(
"i = "
+ i +
", j = "
+ j);
j =
2
;
}
static
int
x1 = prt(
"static Insect.x1 initialized"
);
static
int
prt(String s) {
System.out.println(s);
return
3
;
}
}
public
class
Beetle
extends
Insect {
int
k = prt(
"Beeklt.k initialized"
);
Beetle() {
prt(
"k = "
+ k);
prt(
"j = "
+ j);
}
static
int
x2 = prt(
"static Bootle.x2 initialized"
);
static
int
prt(String s) {
System.out.println(s);
return
4
;
}
public
static
void
main(String[] args) {
prt(
"Beetle constructor"
);
Beetle b =
new
Beetle();
}
}
|
【运行结果】:
static Insect.x1 initialized
static Bootle.x2 initialized
Beetle constructor
i = 1, j = 0
Beeklt.k initialized
k = 4
j = 2
对Beetle运行Java时,发生的第一件事情是装载程序到外面找到那个类。在装载过程中,装载程序发现一个基础类,所以随之将其载入。无论是否生成基础类的对象,这一过程都将执行。如果基础类含有另一个基础类,则另一个基础类随即也会载入,以此类推。接下来就在根基础类中执行static初始化,再在下一个衍生类中执行,以此类推。这是因为衍生类的初始化可能要依赖于对基础类成员的初始化。
当类都装载完毕,就能创建对象。首先,这个对象中的所有基本数据类型都会设置成为他们的默认值,对象句柄设为null。然后执行基础类的构建器。这种情况是自动完成的(衍生类的构造函数中默认调用了super(),
也可以通过super指定基类的构建器)。基础类构建器完成后,衍生类实例变量就会按本来的顺序得到初始化,然后执行构建器的剩余的主体部分。
总结对象创建的过程:
静态只在类加载的时候执行且只执行一次;
非静态只有在实例化的时候执行,每次创建对象都执行;
静态在非静态之前执行,基类静态优先于衍生类静态执行;
静态属性和静态代码块的执行属性取决于它们在类中的位置,谁在前先执行谁;
非静态属性和构造块的执行顺序取决于它们在类中的位置,谁在前执行谁。
总结
通过上面的介绍,我们对Java中初始化对象的几种方式以及通过何种方式执行初始化代码有了了解,同时也对何种情况下我们可能会使用到未经初始化的变量进行了介绍。在对这些问题有了详细的了解之后,就可以在编码中规避一些风险,保证一个对象在可见之前是完全被初始化的。