问题描述
在Java里声明成员变量不需赋初始值,如果声明局部变量则必须赋初始值,否则编译器会报错并且强制赋值,关于这个问题,先看下面的Demo:
- /**
- *
- * @author Joher
- * @date 2012-6-8 10:47:29
- */
- public class VariableTest
- {
- private String userName; //成员变量不需要赋初始值
- private int age;
-
- public static void main(String[] args)
- {
- VariableTest t = new VariableTest();
- System.out.println(t.userName); //正确编译并输出变量初始值null
- System.out.println(t.age); //正确编译并输出变量初始值0
- String nickName; //局部变量则需要赋初始值,否则无法通过编译
- System.out.println(nickName); //此处报错:the local variable nickName may not have been initialized
- }
- }
复制代码
问题分析
Java里无法实现这样的操作所以无法用Java来举例。 可以试试这段C代码:
- #include <stdio.h>
- void foo() {
- int i = 0x7F7F7F7F;
- }
- void bar() {
- int i;
- printf("0x%X", i); // 0x7F7F7F7F, when compiled with GCC 3.4.5
- }
- int main() {
- foo();
- bar();
- return 0;
- }
复制代码
用GCC 3.4.5在MinGW/Windows XP SP3上编译(不要开-O2开关)并运行,可以看到bar()的调用虽然没有设置局部变量i的初始值,但它却与foo()里的i的值是一样的,都是一个不常见的数值,0x7F7F7F7F。选择这样的数值主要是为了避开一些很常见的数字,避免影响测试。
为什么会这样呢?因为局部变量是在运行时调用栈上分配空间的。foo()与bar()正好都需要同样多的栈空间来容纳参数(无)与局部变量(都是一个int),所以两者的局部变量i先后被分配到了同一个地址上。foo()被调用时,将这个地址的值设为了0x7F7F7F7F,然后bar()被调用的时候没有改变这个地址的值并且直接读取来用了,所以有上面的输出结果。
注意这个例子不要开-O2开关是因为不开优化的时候,GCC 3.4.5确实会把局部变量分配在栈里,而开了优化后许多局部变量很可能根本不会出现在栈里,而是直接分配在寄存器上了。这样虽然也可以看到bar()调用时i不为0,但无法保证是foo()里设置的i的值,效果没这么明显。
另外需要注意的是并不是所有编译器在同一个平台上编译都会得到一样的结果,同一个编译器在不同平台上的结果也很可能是不同的。这段代码用VC9来编译得到的行为就不同。C的语言规范允许这些不同,因为未初始化的局部变量的值被规定为“未定义的”,也就是可以是任意值。
Java里的局部变量从概念上说也是在运行时调用栈上分配空间的,因而如果不进行确定性的初始化,就可能会出现跟上面类似的潜在问题。
当然事实上经过JIT之后Java方法里的局部变量同样可能被优化到直接使用寄存器而不在栈上保存,但这是JVM内部的实现细节,对上层的Java语言是透明的。
问题结论
Java中成员变量有默认初始化,也就是如果不显式设置初始值的话就会被初始化为其类型的默认值(0、false、null等)。
Java中的局部变量没有默认初始化。为了减少因为缺乏初始化而带来的程序错误隐患,Java要求局部变量在使用前必须显式设置初始值,也就是说在某个变量第一次被读取前在任意代码路径上都必须对它至少有一次赋值操作。 |