为什么这个Java代码编译?

在方法或类范围内,下面的代码编译(带警告):

int x = x = 1; 

在类范围中, variables获取其默认值时 ,以下给出“未定义的引用”错误:

 int x = x + 1; 

是不是第一个x = x = 1应该以相同的“未定义参考”错误? 或者也许第二行int x = x + 1应该编译? 或者有什么我失踪?

TL;博士

对于字段int b = b + 1是非法的,因为b是对b的非法前向引用。 你可以通过编写int b = this.b + 1来解决这个问题,这个编译没有任何抱怨。

对于局部variablesint d = d + 1是非法的,因为d在使用前没有被初始化。 对于总是默认初始化的字段,情况并非如此。

您可以通过尝试编译来看到差异

int x = (x = 1) + x;

作为一个字段声明和一个局部variables声明。 前者会失败,但由于语义的不同,后者将会成功。

介绍

首先,字段和局部variables初始化器的规则是非常不同的。 所以这个答案将分两部分来处理规则。

我们将全程使用这个testing程序:

 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节 “字段的初始化”进行pipe理。

JLS第6.3节 “声明的范围”中定义了一个字段的范围。

相关规则是:

  • 在类typesC(§8.1.6)中声明或inheritance的成员m声明的范围是C的整个主体,包括任何嵌套types声明。
  • 实例variables的初始化expression式可以使用在类中声明或由类inheritance的任何静态variables的简单名称,即使是稍后以文本方式发生的声明。
  • 尽pipe这些实例variables在范围之内,但是在使用之后使用声明在文本上出现的实例variables有时会受到限制。 有关对实例variables进行前向引用的精确规则,请参阅第8.3.2.3节。

§8.3.2.3说:

成员声明只有在成员是类或接口C的实例(分别为静态)字段并且以下所有条件成立时,才需要以文本方式显示:

  • 用法发生在C的一个实例(分别是静态)variables初始值设定项或C中的一个实例(分别是静态)初始值设定项中。
  • 用法不在作业的左侧。
  • 用法是通过一个简单的名字。
  • C是封闭用法的最里面的类或接口。

实际上,除了在某些情况下,您可以在声明之前引用这些字段。 这些限制是为了防止类似的代码

 int j = i; int i = j; 

从编译。 Java规范说“上面的限制是为了在编译时捕获循环或其他格式不正确的初始化。

这些规则实际上归结为什么?

总之,规则基本上说,如果(a)引用是在初始化器中,(b)引用没有被分配给,(c)引用是一个引用,那么你必须在引用该字段之前声明一个字段简单的名字 (没有像this.限定词)和(d)它不在内部类中被访问。 因此,满足所有四个条件的前向引用是非法的,但是在至less一个条件下失败的前向引用是可以的。

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仍然是非法的前向引用。

局部variables

局部variables声明受JLS第14.4节 “局部variables声明语句”的pipe辖。

局部variables的范围在JLS第6.3节 “声明的范围”中定义:

  • 块(14.4)中的局部variables声明的范围是声明出现的块的其余部分,从其自己的初始化程序开始,并在本地variables声明语句右侧包含任何其他声明程序。

请注意,初始化程序在声明的variables的范围内。 那么为什么不int d = d + 1; 编译?

原因是Java对明确赋值的规则( JLS§16 )。 明确的赋值基本上说,每个对局部variables的访问都必须具有对该variables的前一个赋值,Java编译器检查循环和分支以确保赋值始终在任何使用之前发生(这就是为什么明确赋值具有专用的整个规范部分到它)。 基本规则是:

  • 对于每个访问局部variables或空白的最终字段x ,必须在访问之前明确指定x ,否则会发生编译时错误。

int d = d + 1; ,对d的访问被parsing为局部variablesfi​​ne,但是由于在访问d之前d没有被赋值,编译器会发出一个错误。 在int c = c = 1c = 1首先发生,它赋值c ,然后c被初始化为赋值结果(即1)。

请注意,由于明确的分配规则,局部variables声明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>; 

在这种情况下,你的expression式是x = 1 ,这也是一个声明。 x = 1是一个有效的语句,因为var x已被声明。 它也是一个值为1的expression式,然后再赋给x

在Java或任何现代语言中,赋值来自右边。

假设如果你有两个variablesx和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; 

这里没有理由抛出未定义的引用错误。 现在在初始化之前有variables的使用,所以这个代码完全符合规范。 实际上根本没有使用variables ,只是任务。 而且JIT编译器会更进一步,它会消除这样的结构。 说实话,我不明白这个代码是如何连接到JLS的variables初始化和使用的规范。 没有用法没有问题。 ;)

如果我错了,请纠正。 我不知道为什么其他答案,这涉及到许多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 

最右边的expression式只是分配给variables,没有任何recursion。 我们可以用任何方式搞乱variables:

 a = b = c = f = e = d = a = a = a = a = a = e = f = 5; 

int x = x + 1; 你给x添加1,那么x的值是什么,它还没有创build。

但是在int x=x=1; 将编译没有错误,因为您将1分配给x

你的第一块代码包含第二个=而不是一个加号。 这将在任何地方编译,而第二段代码不会在任何地方编译。

在第二段代码中,x在声明之前被使用,而在第一段代码中它被分配两次,这是没有意义的,但是是有效的。

让我们一步一步分解吧,正确的联想

 int x = x = 1 

x = 1 ,给variablesx赋值1

int x = x ,将int x = x指定给自己,作为int。 由于x先前被指定为1,因此保留1,尽pipe是冗余的。

编译好。

 int x = x + 1 

x + 1 ,给variablesx加1。 但是,x未定义,这将导致编译错误。

int x = x + 1 ,因此这条线编译错误,因为等号的右边部分不会编译,将一个添加到未指定的variables

第二个int x=x=1是编译的,因为你将值赋给x,但是在其他情况下int x=x+1这里variablesx没有被初始化,记住在java局部variables中没有初始化为默认值。 注意如果它是( int x=x+1 )在类范围内,那么也会给编译错误,因为这个variables没有被创build。

 int x = x + 1; 

在Visual Studio 2008中成功编译并出现警告

 warning C4700: uninitialized local variable 'x' used` 

x在x = x + 1 ;中未被初始化。

Java编程语言是静态types的,这意味着所有variables必须先声明才能使用。

查看原始数据types

由于代码的实际工作方式,代码行不会编译出警告。 当你运行代码int x = x = 1 ,Java首先根据定义创buildvariablesx然后运行分配代码( 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 

编译器从右向左读取语句,我们devise做相反的事情。 这就是为什么起初它感到烦恼。 使这个习惯从右到左阅读语句(代码),你将不会有这样的问题。