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

Why can't we access static content via uninitialized local variable?

看看下面的代码:

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 时看到的那样,代码 foo.x 生成编译错误:Variable 'foo' might not have been initialized.

它可能 看起来 这样的错误是有道理的,但只有在我们意识到要访问 static 成员之前JVM实际上并不使用变量的,而只是它的 类型.

例如,我可以用值 null 初始化 foo,这样我们就可以毫无问题地访问 x

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

这样的场景是可行的,因为编译器意识到 x 是静态的,并且将 foo.x 视为像 Foo.x 那样写的(至少我到现在为止是这么想的)。

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


免责声明:这不是在实际应用中使用的代码,而是我在 Stack Overflow 上找不到答案的有趣现象,所以我决定问一下。

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

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

System.out.println(Foo.x);

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

Chapter 16. Definite Assignment

Each local variable (§14.4) and every blank final field (§4.12.4, §8.3.1.2) must have a definitely assigned value when any access of its value occurs.

您尝试通过局部变量访问什么并不重要。规则是它应该在此之前明确分配。

要计算 a field access expression foo.x, the primary 的一部分 (foo) 必须先计算。这意味着将发生对 foo 的访问,这将导致编译时错误。

For every access of a local variable or blank final field x, x must be definitely assigned before the access, or a compile-time error occurs.

§15.11. Field Access Expressions:

If the field is static:

The Primary expression is evaluated, and the result is discarded. If evaluation of the Primary expression completes abruptly, the field access expression completes abruptly for the same reason.

前面指出字段访问由 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() 即使它总是 returns a null 被丢弃的值。

如果不计算表达式,print 会立即出现,当 class Foo 直接用于访问 x 时可以看到 Foo.x.

注意: 方法调用也被认为是 §15.8 Primary Expressions.

中显示的 Primary

其他答案完美地解释了正在发生的事情背后的机制。也许您还想了解 Java 规范背后的基本原理。不是 Java 专家,我不能给你最初的理由,但让我指出:

  • 每段代码要么有意义,要么触发编译错误。
  • (对于静力学,因为实例是不必要的,所以Foo.x是自然的。)
  • 现在,我们要用foo.x做什么(通过实例变量访问)?
    • 可能是编译错误,如在 C# 中,或者
    • 有一定的意义。因为Foo.x已经表示"simply access x",所以表达式foo.x有不同的含义是合理的;也就是说,表达式的每个部分都是有效的 并且访问 x.

希望有识之士告知真正原因。 :-)