为什么java在使用未初始化的变量时会区别对待类作用域和方法作用域



Java不允许您使用可能尚未在方法范围内初始化的变量。类作用域内未初始化的变量仍可能由类方法返回,并且值默认为null。

为什么两种不同的处理范围不同?

public class TestClass {
    Integer i;
    Double d;
    public TestClass() {
        d = 1d;
    }
    public Double getD() {
        return d;
    }
    public Integer getI() {
        return i;
    }
//  public Integer getSomeInt() {
//      Integer i;
//      return i;
//  }
    public static void main(String[] args) {
        TestClass myClass = new TestClass();
        System.out.println(myClass.getI().getClass());
    }
}

这会导致NullPointerException,但在getSomeInt()中返回i是编译器错误,因为"变量可能尚未初始化"。

这背后的原因是Java静态代码分析的局限性。编译器能够毫无疑问地证明,在初始化之前不会读取堆栈分配的本地var。这对于堆分配的内存来说是不可能的,因此Java要求在暴露指向它的指针之前,将所有堆分配的存储清零。

该规则的结果是,堆分配的所有内容都有一个默认值零(false、null,无论二进制零对类型的意义如何)。

因为成员变量有默认值(如果未初始化),所以Inull,如果在null上调用方法,则会导致NullPointerException

对于局部变量,在使用之前必须对其进行初始化,否则它将变成编译时错误

局部变量略有不同;编译器从不为未初始化的局部变量分配默认值。如果无法在声明的地方初始化本地变量,请确保在尝试使用它之前为其赋值。访问未初始化的本地变量将导致编译时错误。[….]

这真的很简单。成员变量会自动初始化为默认值,而局部变量则不会。

当你做

public Integer getSomeInt() {
    Integer i;
    return i;
}

隐藏this.i,而在return i中,您引用了一个(未初始化的)本地变量。


那么为什么成员变量是自动初始化的,而局部变量不是?

最终,这是一个只有语言设计者才能回答的问题,但如果非要我猜测的话,我会说这是由于必须将所有分配的内存清零的性能问题。然而,当涉及到对象时,强迫程序员显式初始化所有字段将是一件痛苦的事情。


来自JLS(4.12.3种变量):

类变量是在准备好其类或接口时创建的(§12.3.2),并且初始化为默认值(§4.12.5)。

[…]

局部变量声明语句可能包含一个表达式初始化变量。带有初始化表达式的局部变量为但是,直到局部变量声明语句声明它已被执行。(确定性赋值规则(第16章,确定性赋值)阻止局部变量的值在已初始化或以其他方式分配了值。)

在调用super之后,所有字段都在构造函数中隐式初始化。对象引用设置为null,基元值设置为0false等。这种隐式初始化不是在方法中完成的。

最新更新