Java 数组是引用类型,为什么局部变量未初始化仍会编译报错?
正确答案是 A。c 是局部变量,Java 不会自动将它初始化为 null。执行 met(c) 时需要读取 c 的值,而它尚未赋值,因此代码无法编译。
数组是引用类型,但变量是否有默认初始值,还要看它是局部变量、字段还是数组元素。
为什么引用类型的默认值规则不适用于 c?
Java Language Specification §4.12.5 规定,类变量、实例变量和数组元素在创建时会获得默认值,引用类型的默认值是 null。同一节还要求局部变量在使用前通过初始化或赋值获得值,并满足确定赋值(definite assignment)规则。
原代码中:
int[] c;
met(c);
第一行只声明了一个 int[] 类型的局部变量,没有创建数组,也没有给 c 赋值。第二行把 c 作为实参传入方法,需要读取它的值,因此在调用处就会出现编译错误,无法进入 met。此时 c 尚未赋值,不能说它的值为 null。
数组、数组引用和数组元素有什么区别?
int 是基本类型,int[] 是引用类型。数组对象的元素可以是基本类型,也可以是引用类型。变量 c 用于保存数组引用,而不是整个数组的内容;引用的值也可以是 null,表示没有引用任何对象。JLS §4.3 对引用类型作了定义。
例如:
int[] c = new int[3];
System.out.println(c.length); // 3
System.out.println(c[0]); // 0
这里显式创建了一个数组,并用它的引用初始化 c。数组中的三个 int 元素自动初始化为 0,元素的默认值不会决定数组的长度。
怎样修改代码,分别得到 B 和 C 的结果?
保留原来的 met 方法,只修改 main 中的声明即可。
如果显式赋值为 null:
int[] c = null;
met(c);
代码可以编译。调用时,参数 a 的值为 null。随后执行 a.length 时,由于没有可读取长度的数组对象,会抛出 NullPointerException,对应 B。
如果希望输出 0,可以创建一个长度为零的数组:
int[] c = new int[0];
met(c); // 输出 0
长度为零的数组是实际存在的数组对象,它的引用与 null 不同。
也可以先声明,稍后再赋值。编译器需要能够确定,每条到达使用位置的执行路径都已完成赋值:
int[] c;
c = new int[0];
met(c); // 输出 0
为什么 Sack 中的 s 可以是 null?
原问题没有提供 Sack 的代码,无法核对具体行号。按描述,如果 s 声明在类体中、方法和构造器之外,并且没有 static,它就是实例字段,不是局部变量。
下面是一个说明规则的示例:
class Sack {
private int[] s;
void print() {
System.out.println(s); // 输出 null
}
}
创建 Sack 对象时,引用类型实例字段 s 会先获得默认值 null,之后字段初始化器、实例初始化块或构造器代码还可能改变它。这个默认值不需要由构造器中的显式赋值语句提供。
private 控制访问权限,不决定变量是否为局部变量。这里要看声明位置:c 声明在 main 方法体内,s 则是类的实例字段。
在这个 int[] 示例中,System.out.println(s) 不需要访问数组成员,因此可以打印 null。s.length 则需要访问实际的数组对象,当 s 为 null 时会抛出 NullPointerException。
备注:内容仅供参考。
