4.6.3 引用类型数组的初始化

4.6.3 引用类型数组的初始化

引用类型数组的数组元素是引用,因此情况变得更加复杂。每个数组元素里存储的还是引用,它指向另一块内存,这块内存里存储了有效数据。

程序示例 引用类型数组 初始化 示意图

为了更好地说明引用类型数组的运行过程,下面先定义一个Person类:

1
2
3
4
5
6
7
8
9
class Person {
public int age; // 年龄
public double height; // 身高
// 定义一个info方法

public void info() {
System.out.println("我的年龄是:" + age + ",我的身高是:" + height);
}
}

下面程序将定义一个Person数组,接着动态初始化这个Person数组,并为这个数组的每个数组元素指定值。

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
public class ReferenceArrayTest {
public static void main(String[] args) {
// 定义一个students数组变量,其类型是Person[]
Person[] students;
// 执行动态初始化
students = new Person[2];
// 创建一个Person实例,并将这个Person实例赋给zhang变量
Person zhang = new Person();
// 为zhang所引用的Person对象的age、height赋值
zhang.age = 15;
zhang.height = 158;
// 创建一个Person实例,并将这个Person实例赋给lee变量
Person lee = new Person();
// 为lee所引用的Person对象的age、height赋值
lee.age = 16;
lee.height = 161;
// 将zhang变量的值赋给第一个数组元素
students[0] = zhang;
// 将lee变量的值赋给第二个数组元素
students[1] = lee;
// 下面两行代码的结果完全一样,因为lee
// 和students[1]指向的是同一个Person实例。
lee.info();
students[1].info();
}
}

上面代码的执行过程代表了引用类型数组初始化的典型过程。下面将结合示意图详细介绍这段代码的执行过程。
执行Person[] students;代码时,这行代码仅仅在栈内存中定义了一个引用变量,也就是一个指针,这个指针并未指向任何有效的内存区。此时内存中存储示意图如图4.8所示:
这里有一张图片
在如图4.8所示的栈内存中定义了一个students变量,它仅仅是一个引用,并未指向任何有效的内存。直到执行初始化,本程序对students数组执行动态初始化,动态初始化由系统为数组元素分配默认的初始值:null,即每个数组元素的值都是null执行动态初始化后的存储示意图如图4.9所示
这里有一张图片
从图4.9中可以看出,students数组的两个数组元素都是引用,而且这个引用并未指向任何有效的内存,因此每个数组元素的值都是null。这意味着依然不能直接使用students数组元素,因为每个数组元素都是null,这相当于定义了两个连续的Person变量,但这个变量还未指向任何有效的内存区,所以这两个连续的Person变量(students数组的数组元素)还不能使用。

接着的代码定义了zhanglee两个Person实例,定义这两个实例实际上分配了4块内存,在栈内存中存储了zhanglee两个引用变量,还在堆内存中存储了两个Person实例。此时的内存存储示意图如图4.10所示。
这里有一张图片
此时students数组的两个数组元素依然是null,直到程序依次将zhang赋给students数组的第一个元素,把lee赋给students数组的第二个元素,students数组的两个数组元素将会指向有效的内存区。此时的内存存储示意图如图4.11所示。
这里有一张图片
从图4.11中可以看出,此时zhangstudents[0]指向同一个内存区,而且它们都是引用类型变量,因此通过zhangstudents[0]来访问Person实例的实例变量和方法的效果完全一样,不论修改students[0]所指向的Person实例的实例变量,还是修改zhang变量所指向的Person实例的实例变量,所修改的其实是同一个内存区,所以必然互相影响。同理,leestudents[1]也是引用同一个Person对象,也具有相同的效果。