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
时看到的
codefoo.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
是首选(这就是为什么我们在尝试使用variantFoo.x
时通常会收到编译警告)。有趣的是,javac
确实做到了这一点,生成加载和弹出指令,而ecj
强制执行正式规则,即,不允许通过未初始化的变量进行访问,但不生成t的代码