tl;dr
For fields, int b = b + 1
是非法的,因为b
是非法的前向引用b
。你实际上可以通过写来解决这个问题int b = this.b + 1
,编译时没有任何抱怨。
For 局部变量, int d = d + 1
是非法的,因为d
使用前未初始化。这是not字段的情况就是这样,它们总是默认初始化的。
您可以通过尝试编译来看到差异
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
error.
的声明d
无效并失败并显示variable d might not have been initialized
error.
这些错误不同的事实应该暗示错误的原因也不同。
Fields
Java 中的字段初始值设定项由以下规则控制JLS §8.3.2 http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.2,字段初始化。
The scope字段的定义是JLS §6.3 http://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.3,声明的范围。
相关规则是:
- 成员声明的范围
m
在类类型 C 中声明或继承的类型 C(第 8.1.6 节)是 C 的整个主体,包括任何嵌套类型声明。
- 实例变量的初始化表达式可以使用在类中声明或继承的任何静态变量的简单名称,即使其声明在文本上稍后出现。
- 其声明在使用后以文本形式出现的实例变量的使用有时会受到限制,即使这些实例变量在作用域内。有关管理对实例变量的前向引用的精确规则,请参阅第 8.3.2.3 节。
§8.3.2.3 说:
成员的声明需要以文本形式出现在之前
仅当成员是实例(分别是静态)字段时才使用
类或接口 C 并且满足以下所有条件:
- 该用法发生在 C 的实例(分别为静态)变量初始值设定项或 C 的实例(分别为静态)初始值设定项中。
- 用法不在赋值的左侧。
- 用法是通过一个简单的名称。
- C 是包含用法的最内部类或接口。
实际上,您可以在声明字段之前引用字段,但某些情况除外。这些限制旨在防止类似的代码
int j = i;
int i = j;
从编译。 Java 规范表示“上述限制旨在在编译时捕获循环或其他格式错误的初始化。”
这些规则实际上归结为什么呢?
简而言之,规则基本上是说你must如果 (a) 引用位于初始值设定项中,(b) 引用未分配给,(c) 引用是一个,则在对该字段的引用之前声明该字段简单的名字(没有像这样的限定符this.
) 和 (d) 它不是从内部类内部访问的。因此,满足所有四个条件的前向引用是非法的,但至少在一个条件上失败的前向引用是可以的。
int a = a = 1;
编译是因为它违反了 (b):引用a
is被分配给,所以引用是合法的a
提前的a
的完整声明。
int b = this.b + 1
也可以编译,因为它违反了 (c):引用this.b
不是一个简单的名称(它的限定条件是this.
)。这个奇怪的结构仍然是完美定义的,因为this.b
其值为零。
因此,基本上,初始化器中对字段引用的限制会阻止int a = a + 1
从成功编译开始。
观察字段声明int b = (b = 1) + b
will fail编译,因为最终b
仍然是非法的前向引用。
局部变量
局部变量声明受以下约束JLS§14.4 http://docs.oracle.com/javase/specs/jls/se7/html/jls-14.html#jls-14.4,局部变量声明语句。
The scope局部变量的定义在JLS §6.3 http://docs.oracle.com/javase/specs/jls/se7/html/jls-6.html#jls-6.3, 声明范围:
- 块中局部变量声明的范围(第 14.4 节)是该声明出现的块的其余部分,从其自己的初始值设定项开始,并包括局部变量声明语句右侧的任何其他声明符。
请注意,初始值设定项位于所声明的变量的范围内。那么为什么不呢int d = d + 1;
编译?
原因是Java的规则明确的分配 (JLS §16 http://docs.oracle.com/javase/specs/jls/se7/html/jls-16.html)。明确赋值基本上是说,对局部变量的每次访问都必须先对该变量进行赋值,并且 Java 编译器会检查循环和分支以确保赋值always发生在任何使用之前(这就是为什么明确赋值有一个专门的规范部分)。基本规则是:
- 对于局部变量或空白最终字段的每次访问
x
, x
必须在访问之前明确赋值,否则会发生编译时错误。
In int d = d + 1;
,访问d
很好地解析为局部变量,但是因为d
之前没有被分配过d
被访问时,编译器会发出错误。在int c = c = 1
, c = 1
首先发生,这会分配c
, 进而c
被初始化为该赋值的结果(即 1)。
请注意,由于明确的赋值规则,局部变量声明int d = (d = 1) + d;
will编译成功(unlike字段声明int b = (b = 1) + b
), 因为d
肯定是在决赛时分配的d
到达了。