为什么我们不能通过未初始化的局部变量访问静态内容?


77

看下面的代码:

class Foo{
    public static int x = 1;
}

class Bar{    
    public static void main(String[] args) {
        Foo foo;
        System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
    }
}

如您所见,尝试x通过未初始化的局部变量Foo foo;代码访问静态字段时会foo.x生成编译错误:Variable 'foo' might not have been initialized

它可能看起来像这样的错误是有道理的,但直到我们意识到,访问static成员的JVM不实际使用价值变量,但只有它的类型

例如,我可以foo使用value进行初始化,null这将使我们能够x毫无问题地进行访问:

Foo foo = null;
System.out.println(foo.x); //compiles and at runtime prints 1!!! 

之所以如此,x是因为编译器意识到这是静态的,并foo.x像对待其编写时一样对待Foo.x(至少这是我到目前为止所认为的)。

那么,为什么编译器突然坚持要foo一个根本不会使用的值?


免责声明:这不是在实际应用中使用的代码,而是一种有趣的现象,我在Stack Overflow上找不到答案,因此我决定对此进行询问。


7
我会说编译器中的一个限制实际上并不值得修复,因为无论如何该代码都会引发警告。
M Anouti

1
@manouti这也是我的猜测,但是我仍然对编译器为何如此表现感兴趣。规范的哪一部分是强制性的?
Pshemo

9
@portfoliobuilder此处没有NPE的风险,因为如上所述,在访问static成员编译器时,如果不使用变量的,而使用变量的类型,则不会发生NPE 。我们甚至可以编写代码((Foo)null).x并且由于编译器会识别出它x是静态的(除非我误解了您的评论),因此它可以编译并工作
Pshemo

29
foo.x首次创建Java时,从非静态上下文(例如)访问静态变量应该是编译器错误。可悲的是,那艘船航行了25年前,如果现在改变的话,那将是一个巨大的改变。
Powerlord

2
@portfoliobuilder“ ..绝对有风险”您想到什么风险?也是一个nitpick:两种方法在技术上都是正确的(不幸的是),但是Foo.x首选的(这就是为什么当我们尝试使用variant时,我们通常会收到编译警告的原因foo.x)。
Pshemo

Answers:


74

§15.11。字段访问表达式

如果字段是静态的

计算主表达式,并丢弃结果。如果主表达式的评估突然完成,则字段访问表达式由于相同的原因而突然完成。

它在更早的地方指出通过标识字段访问 Primary.Identifier

这表明,即使它似乎不使用Primary,也将对其进行评估,然后丢弃结果,这就是为什么需要对其进行初始化的原因。如评估中所述,当报价中止访问时,这可能会有所不同。

编辑:

这是一个简短的示例,目的Primary是直观地证明即使丢弃了结果,也对进行了评估:

class Foo {
    public static int x = 1;
    
    public static Foo dummyFoo() throws InterruptedException {
        Thread.sleep(5000);
        return null;
    }
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(dummyFoo().x);
        System.out.println(Foo.x);
    }
}

在这里,您可以看到该dummyFoo()值仍在评估中,因为尽管总是返回一个被丢弃的值,但该print延迟了5秒。Thread.sleep()null

如果表达式未评价的print会瞬间出现,其可以当类中可以看出Foo,直接用于访问xFoo.x

注意: 方法调用也被视为§15.8主表达式中Primary所示。


4
有趣的是,javac从字面上看,这样做会生成一条加载和弹出指令,而会ecj执行形式规则,即,不允许通过未初始化的变量进行访问,但不会为无副作用的操作生成代码。
Holger

21

第16章明确分配

在访问任何值时,每个局部变量(第14.4节)和每个空白空白字段(第4.12.4节,第8.3.1.2节)都必须具有一个明确分配的值。

您尝试通过局部变量访问的内容并不重要。规则是必须在此之前明确分配它。

要评估字段访问表达式 foo.x,必须首先评估其primary部分(foo)。这意味着foo将发生对的访问,这将导致编译时错误。

对于每次访问局部变量或空白的最终字段x,必须在访问之前明确分配x,否则会发生编译时错误。


14

保持规则尽可能简单有其价值,“不要使用可能尚未初始化的变量”非常简单。

更重要的是,有一种建立静态方法的方法-始终使用类名,而不是变量。

System.out.println(Foo.x);

变量“ foo”是不必要的开销,应删除,并且编译器错误和警告可以被视为有助于实现这一目标。


3

其他答案可以完美地解释发生了什么的机制。也许您还想要Java规范背后的原理。不是Java专家,我无法给出最初的原因,但请允许我指出:

  • 每段代码要么都有含义,要么触发编译错误。
  • (对于静态对象,因为不需要实例,所以Foo.x很自然。)
  • 现在,我们该怎么办foo.x(通过实例变量访问)?
    • 可能是编译错误,例如在C#中,或者
    • 它有一个含义。因为Foo.x已经意味着“简单访问x”,所以表达式 foo.x具有不同的含义是合理的;也就是说,表达式的每个部分都是有效和access x

希望有知识的人能说出真正的原因。:-)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.