当一个对象被创建之后,虚拟机会为其分配内存,主要用来存放对象的实例变量及其从超类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间)。在为这些实例变量分配内存的同时,这些实例变量也会被赋予默认值。
引用
关于实例变量隐藏
1
2
3
4
5
6
7
8
9
10
11
|
class
Foo {
int
i =
0
;
}
class
Bar
extends
Foo {
int
i =
1
;
public
static
void
main(String... args) {
Foo foo =
new
Bar();
System.out.println(foo.i);//0
System.out.println(((Bar)foo).i);//1
}
}
|
上面的代码中,Foo和Bar中都定义了变量i,在main方法中,我们用Foo引用一个Bar对象,如果实例变量与方法一样,允许被覆盖,那么打印的结果应该是1,但是实际的结果确是0。
但是如果我们在Bar的方法中直接使用i,那么用的会是Bar对象自己定义的实例变量i,这就是隐藏,Bar对象中的i把Foo对象中的i给隐藏了,这条规则对于静态变量同样适用。
在内存分配完成之后,java的虚拟机就会开始对新创建的对象执行初始化操作,因为java规范要求在一个对象的引用可见之前需要对其进行初始化。在Java中,三种执行对象初始化的结构,分别是实例初始化器、实例变量初始化器以及构造函数。
,如果我们显式调用超类的构造函数,那么调用指令必须放在构造函数所有代码的最前面,是构造函数的第一条指令。这么做才可以保证一个对象在初始化之前其所有的超类都被初始化完成。
如果我们在一个构造函数中调用另外一个构造函数,如下所示,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
class
ConstructorExample {
private
int
i;
ConstructorExample() {
this
(
1
);
....
}
ConstructorExample(
int
i) {
....
this
.i = i;
....
}
}
|
对于这种情况,Java只允许在ConstructorExample(int i)内出现调用超类的构造函数,也就是说,下面的代码编译是无法通过的,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
ConstructorExample {
private
int
i;
ConstructorExample() {
super
();
this
(
1
);
....
}
ConstructorExample(
int
i) {
....
this
.i = i;
....
}
}
|
或者,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
ConstructorExample {
private
int
i;
ConstructorExample() {
this
(
1
);
super
();
....
}
ConstructorExample(
int
i) {
....
this
.i = i;
....
}
}
|
Java对构造函数作出这种限制,目的是为了要保证一个类中的实例变量在被使用之前已经被正确地初始化,不会导致程序执行过程中的错误。