在方法或类范围内,以下行编译(带有警告):
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声明的范围中定义。
相关规则如下:
m
C 的整个主体,包括任何嵌套类型声明。§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 基本上执行代码就好像它是这样的:x
x = 1
x
x
x
x
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
编译器从右到左读取语句,而我们设计相反。这就是为什么它一开始很生气的原因。养成从右到左阅读语句(代码)的习惯,您就不会有这样的问题。