[英]Why does this Java code compile?
在方法或类范围中,下面的行编译(带警告):
int x = x = 1;
在类范围中, 变量获取其默认值 ,以下给出“未定义引用”错误:
int x = x + 1;
是不是第一个x = x = 1
应该以相同的'undefined reference'错误结束? 或者第二行int x = x + 1
应该编译? 或者有些东西我不见了?
对于字段 , int b = b + 1
是非法的,因为b
是非法的前向引用b
。 您可以通过编写int b = this.b + 1
来实际修复此问题,该编译无需投诉。
对于局部变量 , int d = d + 1
是非法的,因为d
在使用前未初始化。 对于始终默认初始化的字段,情况并非如此。
您可以通过尝试编译来查看差异
int x = (x = 1) + x;
作为字段声明和局部变量声明。 前者会失败,但后者会成功,因为语义不同。
首先,字段和局部变量初始化程序的规则是非常不同的。 所以这个答案将分两部分来处理这些规则。
我们将在整个过程中使用此测试程序:
public class test {
int a = a = 1;
int b = b + 1;
public static void Main(String[] args) {
int c = c = 1;
int d = d + 1;
}
}
b
的声明无效,并且因illegal forward reference
错误而失败。
d
的声明无效,失败, variable d might not have been initialized
错误。
这些错误不同的事实应该暗示错误的原因也不同。
Java中的字段初始化程序由JLS§8.3.2 ,字段初始化控制。
字段的范围在JLS§6.3 ,声明范围中定义。
相关规则是:
m
声明范围是C的整个主体,包括任何嵌套类型声明。 §8.3.2.3说:
成员声明只有在成员是类或接口C的实例(分别为静态)字段并且满足以下所有条件时才需要以文本方式显示:
- 用法发生在C的实例(分别是静态)变量初始化器或C的实例(分别是静态)初始化器中。
- 用法不在作业的左侧。
- 用法是通过一个简单的名称。
- C是封闭用法的最内层类或接口。
除非在某些情况下,您实际上可以在声明字段之前引用这些字段。 这些限制旨在防止类似的代码
int j = i;
int i = j;
从编译。 Java规范说“上述限制旨在捕获,在编译时,循环或其他错误的初始化。”
这些规则实际归结为什么?
总之,这些规则基本上是说,你必须提前到字段的引用的声明场如果(a)的引用是一个初始化,(B)引用不会被分配到,(c)证明是简单的名称 (没有像this.
限定符this.
)和(d)它不是从内部类中访问的。 因此,满足所有四个条件的前向引用是非法的,但是至少在一个条件上失败的前向引用是可以的。
int a = a = 1;
编译因为它违反了(B):参考a
被分配给了,所以它的法律是指a
提前a
的完整的声明。
int b = this.b + 1
也编译,因为它违反了(c):引用this.b
不是一个简单的名字(它是合格的this.
)。 这个奇怪的构造仍然是完美定义的,因为this.b
的值为零。
因此,基本上,初始化程序中对字段引用的限制会阻止int a = a + 1
被成功编译。
观察到字段声明int b = (b = 1) + b
将无法编译,因为最终b
仍然是非法的前向引用。
局部变量声明由JLS§14.4 ,局部变量声明声明控制。
局部变量的范围在JLS§6.3 ,声明范围中定义:
请注意,初始值设定项在声明的变量范围内。 那么为什么不把int d = d + 1;
编译?
原因在于Java对明确赋值的规则( JLS§16 )。 确定赋值基本上表示对局部变量的每次访问都必须具有对该变量的先前赋值,并且Java编译器检查循环和分支以确保在任何使用之前总是发生赋值(这就是为什么明确赋值具有专用的整个规范部分的原因)它)。 基本规则是:
x
每次访问,必须在访问之前明确分配x
,否则发生编译时错误。 在int d = d + 1;
之后,进入d
解析为局部变量的罚款,但因为d
尚未分配前d
被访问时,编译器会发出一个错误。 在int c = c = 1
,首先发生c = 1
,它分配c
,然后将c
初始化为该赋值的结果(即1)。
注意,由于明确的赋值规则,局部变量声明int d = (d = 1) + d;
将成功编译( 与字段声明int b = (b = 1) + b
)不同,因为d
在到达最后一个d
时明确赋值。
int x = x = 1;
相当于
int x = 1;
x = x; //warning here
而在
int x = x + 1;
首先我们需要计算x+1
但x+1
的值是未知的,所以你得到一个错误(编译器知道x的值是未知的)
它大致相当于:
int x;
x = 1;
x = 1;
首先, int <var> = <expression>;
总是相当于
int <var>;
<var> = <expression>;
在这种情况下,表达式是x = 1
,这也是一个语句。 x = 1
是一个有效的语句,因为已经声明了var x
。 它也是值为1的表达式,然后再将其赋值给x
。
在java或任何现代语言中,赋值来自右侧。
假设你有两个变量x和y,
int z = x = y = 5;
此语句有效,这是编译器分割它们的方式。
y = 5;
x = y;
z = x; // which will be 5
但在你的情况下
int x = x + 1;
编译器给出了一个异常,因为它会像这样分裂。
x = 1; // oops, it isn't declared because assignment comes from the right.
int x = x = 1;
不等于:
int x;
x = 1;
x = x;
javap再次帮助我们,这些是为此代码生成的JVM指令:
0: iconst_1 //load constant to stack
1: dup //duplicate it
2: istore_1 //set x to constant
3: istore_1 //set x to constant
更像:
int x = 1;
x = 1;
这里没有理由抛出未定义的引用错误。 现在在初始化之前使用变量,因此该代码完全符合规范。 实际上根本没有使用变量 ,只是赋值。 而且JIT编译器会更进一步,它将消除这种结构。 说实话,我不明白这段代码是如何连接到JLS的变量初始化和使用规范的。 没用就没问题。 ;)
如果我错了,请更正。 我无法弄清楚为什么其他答案,涉及许多JLS段落收集了这么多的优点。 这些段落与本案没有任何共同之处。 只是两个串行任务,而不是更多。
如果我们写:
int b, c, d, e, f;
int a = b = c = d = e = f = 5;
等于:
f = 5
e = 5
d = 5
c = 5
b = 5
a = 5
最正确的表达式只是逐个分配给变量,没有任何递归。 我们可以以任何方式混淆变量:
a = b = c = f = e = d = a = a = a = a = a = e = f = 5;
在int x = x + 1;
你加1,X,那么什么是价值x
,它尚未创建。
但是在int x=x=1;
将编译没有错误,因为您为x
分配1。
您的第一段代码包含第二个=
而不是加号。 这将在任何地方编译,而第二段代码不会在任何一个地方编译。
在第二段代码中,x在其声明之前使用,而在第一段代码中,它只被分配两次,这没有意义但是有效。
让我们一步一步地分解,正确的联想
int x = x = 1
x = 1
,将1赋值给变量x
int x = x
,将int x = x
指定为自身,作为int。 由于x先前被指定为1,因此它保留1,尽管是多余的方式。
编译好。
int x = x + 1
x + 1
,将一个加到变量x。 但是,x未定义,这将导致编译错误。
int x = x + 1
,因此这行编译错误,因为equals的右边部分将不编译,将一个添加到未分配的变量
第二个int x=x=1
是编译因为你将值赋给x但是在其他情况下int x=x+1
这里变量x没有初始化,记住java局部变量没有初始化为默认值。 注意如果它在类范围内是( int x=x+1
),那么它也会因为没有创建变量而给出编译错误。
int x = x + 1;
在Visual Studio 2008中成功编译并发出警告
warning C4700: uninitialized local variable 'x' used`
由于代码的实际工作方式,代码行不会带有警告进行编译。 当您运行代码int x = x = 1
,Java首先根据定义创建变量x
。 然后它运行赋值代码( x = 1
)。 由于x
已经定义,系统没有错误,将x
设置为1.这将返回值1,因为现在是x
的值。 因此, x
现在最终设置为1。
Java基本上执行代码,就像这样:
int x;
x = (x = 1); // (x = 1) returns 1 so there is no error
但是,在你的第二段代码中, int x = x + 1
, + 1
语句需要定义x
到那时它不是。 由于赋值语句总是意味着首先运行=
右边的代码,因此代码将失败,因为x
未定义。 Java会像这样运行代码:
int x;
x = x + 1; // this line causes the error because `x` is undefined
编译器从右到左阅读语句,我们的目的是做相反的事情。 这就是为什么它一开始很恼火。 从右到左让这个习惯读取语句(代码),你就不会遇到这样的问题。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.