在方法或类范围内,以下行编译(带有警告):
int x = x = 1;
在类范围内,变量获取其默认值,以下给出“未定义引用”错误:
int x = x + 1;
不是第一个x = x = 1应该以相同的“未定义引用”错误结束吗?或者也许第二行int x = x + 1应该编译?或者我缺少什么?
在方法或类范围内,以下行编译(带有警告):
int x = x = 1;
在类范围内,变量获取其默认值,以下给出“未定义引用”错误:
int x = x + 1;
不是第一个x = x = 1应该以相同的“未定义引用”错误结束吗?或者也许第二行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声明的范围中定义。
相关规则如下:
mC 的整个主体,包括任何嵌套类型声明。§8.3.2.3 说:
仅当成员是类或接口 C 的实例(分别为静态)字段并且满足以下所有条件时,成员的声明才需要在使用之前以文本形式出现:
- 该用法出现在 C 的实例(分别为静态)变量初始化器或 C 的实例(分别为静态)初始化器中。
- 用法不在作业的左侧。
- 用法是通过一个简单的名称。
- C 是包含用法的最里面的类或接口。
您实际上可以在声明字段之前引用它们,除非在某些情况下。这些限制旨在防止类似的代码
int j = i;
int i = j;
从编译。Java 规范说“上述限制旨在在编译时捕获循环或其他格式错误的初始化。”
这些规则实际上归结为什么?
简而言之,规则基本上说,如果 (a) 引用在初始化程序中,(b) 引用未分配给,(c) 引用是简单的名称(没有限定符,如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将无法编译,因为 finalb仍然是非法的前向引用。
局部变量声明受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在到达 final 时肯定已赋值d。
int x = x = 1;
相当于
int x = 1;
x = x; //warning here
而在
int x = x + 1;
首先我们需要计算x+1,但是 x 的值是未知的,所以你会得到一个错误(编译器知道 x 的值是未知的)
它大致相当于:
int x;
x = 1;
x = 1;
首先,int <var> = <expression>;总是等价于
int <var>;
<var> = <expression>;
在这种情况下,您的表达式是x = 1,这也是一个语句。x = 1是一个有效的声明,因为 varx已经被声明了。它也是一个值为 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,它的值是多少,它尚未创建。
但是 inint x=x=1;将编译没有错误,因为您将 1 分配给x.
您的第一段代码包含第二个=而不是一个加号。这将在任何地方编译,而第二段代码不会在任何地方编译。
在第二段代码中,x 在其声明之前使用,而在第一段代码中,它只是分配了两次,这没有意义但有效。
让我们一步一步分解,右联想
int x = x = 1
x = 1,将 1 分配给变量 x
int x = x, 将 x 是什么分配给它自己,作为一个 int。由于 x 之前被指定为 1,因此它保留 1,尽管是以冗余方式。
这编译得很好。
int x = x + 1
x + 1,将变量 x 加一。但是, x 未定义这将导致编译错误。
int x = x + 1,因此这一行编译错误,因为等号的右侧部分将无法编译将 1 添加到未分配的变量
第二个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`
由于代码的实际工作方式,该代码行编译时不会出现警告。当您运行代码时,Java 首先按照定义int x = x = 1创建变量。然后它运行分配代码 ( )。由于已经定义,系统没有错误设置为 1。这将返回值 1,因为现在是 的值。因此,现在最终设置为
1。Java 基本上执行代码就好像它是这样的:xx = 1xxxx
int x;
x = (x = 1); // (x = 1) returns 1 so there is no error
但是,在您的第二段代码中,需要定义int x = x + 1该+ 1语句x,到那时它不是。由于赋值语句总是意味着首先运行右边的代码,因此代码将因为未定义=而失败。xJava 会像这样运行代码:
int x;
x = x + 1; // this line causes the error because `x` is undefined
编译器从右到左读取语句,而我们设计相反。这就是为什么它一开始很生气的原因。养成从右到左阅读语句(代码)的习惯,您就不会有这样的问题。