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

Java 为什么可以';我们不能通过未初始化的局部变量访问静态内容吗?,java,static,initialization,local-variables,jls,Java,Static,Initialization,Local Variables,Jls,请看下面的代码: 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 } } 正如您在试图通过未初始化的局部变量Foo

请看下面的代码:

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
    }
}
正如您在试图通过未初始化的局部变量
Foo-Foo访问静态字段
x
时看到的
code
foo.x
生成编译错误:
变量“foo”可能尚未初始化

这个错误可能看起来很有意义,但只有在我们意识到要访问
静态
成员时,JVM才真正使用变量的值,而只使用它的类型

例如,我可以使用值
null
初始化
foo
,这将允许我们访问
x
,而不会出现任何问题:

Foo foo = null;
System.out.println(foo.x); //compiles and at runtime prints 1!!! 
这种情况之所以有效,是因为编译器意识到
x
是静态的,并且将
foo.x
视为像
foo.x
那样编写(至少我到目前为止一直是这么认为的)

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



免责声明:这不是将在实际应用程序中使用的代码,而是一个有趣的现象,我找不到关于堆栈溢出的答案,所以我决定询问它。

让规则尽可能简单是有价值的,而“不要使用可能未初始化的变量”是最简单的

更重要的是,有一种调用静态方法的既定方法——始终使用类名,而不是变量

System.out.println(Foo.x);
变量“foo”是不必要的开销,应该删除,编译器错误和警告可以被视为有助于实现这一点

每个局部变量(§14.4)和每个空白最终字段(§4.12.4,§8.3.1.2)在访问其值时必须有一个明确的赋值。

通过局部变量访问什么并不重要。规则是,在这之前必须明确指定

要评估
foo.x
,必须首先评估其部分(
foo
)。这意味着将访问
foo
,这将导致编译时错误

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

:

如果字段是静态的

对主表达式求值,并丢弃结果。如果主表达式的计算突然完成,则字段访问表达式也会因为相同的原因突然完成

其中,前面指出字段访问由
Primary.Identifier
标识

这表明,即使它似乎没有使用
,仍会对其进行评估,然后丢弃结果,这就是它需要初始化的原因。当评估如报价中所述停止访问时,这可能会产生影响

编辑:

下面是一个简短的示例,仅用于直观地演示即使结果被丢弃,仍会对
进行评估:

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()
仍然被计算,因为
打印
延迟了5秒
线程。sleep()
即使它总是返回一个被丢弃的
null

如果未对表达式求值,
print
将立即显示,这可以在使用
Foo.x
直接使用类
Foo
访问
x
时看到


注意:方法调用也被认为是中所示的
主要

其他答案完美地解释了发生的事情背后的机制。也许您还想了解Java规范背后的基本原理。作为一名Java专家,我无法给出最初的原因,但让我指出:

  • 每段代码要么有意义,要么触发编译错误
  • (对于静态,因为实例是不必要的,
    Foo.x
    是自然的。)
  • 现在,我们应该如何处理
    foo.x
    (通过实例变量访问)?
    • 这可能是编译错误,如在C#中,或
    • 这是有意义的。由于
      Foo.x
      已经表示“简单访问
      x
      ”,因此表达式
      Foo.x
      具有不同的含义是合理的;也就是说,表达式的每个部分都是有效的,可以访问
      x

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

我认为编译器中的一个限制不值得修复,因为代码无论如何都会引发警告。@manouti这也是我的猜测,但我仍然对编译器为什么会这样做感兴趣。规范的哪一部分强制它?@portfoliobuilder这里没有NPE的风险,因为如前所述,在访问
静态
的情况下,成员编译器不使用变量的值,而是使用其类型。我们甚至可以编写
((Foo)null.x
,这将编译并工作,因为编译器将识别
x
是静态的(除非我误解了您的评论)。从非静态上下文(例如
Foo.x
)访问静态变量在Java最初创建时应该是编译器错误。可悲的是,那艘船已经航行了25年多,如果他们现在改变它,那将是一个突破性的改变。@portfoliobuilder“。绝对有风险“你想冒什么风险?还有一点挑剔:两种方法在技术上都是正确的(不幸的是),但
Foo.x
是首选(这就是为什么我们在尝试使用variant
Foo.x
时通常会收到编译警告)。有趣的是,
javac
确实做到了这一点,生成加载和弹出指令,而
ecj
强制执行正式规则,即,不允许通过未初始化的变量进行访问,但不生成t的代码