96

在方法或类范围内,以下行编译(带有警告):

int x = x = 1;

在类范围内,变量获取其默认值,以下给出“未定义引用”错误:

int x = x + 1;

不是第一个x = x = 1应该以相同的“未定义引用”错误结束吗?或者也许第二行int x = x + 1应该编译?或者我缺少什么?

4

14 回答 14

101

tl;博士

对于字段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声明的范围中定义。

相关规则如下:

  • 在类类型 C(第 8.1.6 节)中声明或继承的成员的声明范围是mC 的整个主体,包括任何嵌套类型声明。
  • 实例变量的初始化表达式可以使用在类中声明或由类继承的任何静态变量的简单名称,即使是稍后在文本中声明的静态变量。
  • 有时限制使用在使用后以文本形式出现声明的实例变量,即使这些实例变量在范围内。有关管理对实例变量的前向引用的精确规则,请参见第 8.3.2.3 节。

§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中定义,声明的范围:

  • 块中局部变量声明的范围(第 14.4 节)是声明出现的块的其余部分,从它自己的初始化程序开始,并在局部变量声明语句的右侧包括任何进一步的声明符。

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

原因是由于 Java 的明确分配规则(JLS §16)。确定赋值基本上是说每次访问局部变量都必须先对该变量进行赋值,Java 编译器检查循环和分支以确保赋值总是在任何使用之前发生(这就是为什么确定赋值有一个专门的整个规范部分给它)。基本规则是:

  • 对于局部变量或空白 final 字段的每次访问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

于 2013-04-04T19:26:54.227 回答
86
int x = x = 1;

相当于

int x = 1;
x = x; //warning here

而在

int x = x + 1; 

首先我们需要计算x+1,但是 x 的值是未知的,所以你会得到一个错误(编译器知道 x 的值是未知的)

于 2013-04-04T09:29:02.423 回答
41

它大致相当于:

int x;
x = 1;
x = 1;

首先,int <var> = <expression>;总是等价于

int <var>;
<var> = <expression>;

在这种情况下,您的表达式是x = 1,这也是一个语句。x = 1是一个有效的声明,因为 varx已经被声明了。它也是一个值为 1 的表达式,然后x再次分配给它。

于 2013-04-04T09:24:25.930 回答
12

在 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.
于 2013-04-04T09:38:14.507 回答
8

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;
于 2013-04-04T17:09:21.227 回答
7

int x = x + 1;您将 1 添加到 x 时x,它的值是多少,它尚未创建。

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

于 2013-04-04T09:23:26.727 回答
5

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

于 2013-04-04T09:22:03.547 回答
5

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

于 2013-04-04T09:23:45.883 回答
5

让我们一步一步分解,右联想

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 添加到未分配的变量

于 2013-04-04T15:36:35.387 回答
3

第二个int x=x=1是编译,因为您将值分配给 x 但在其他情况下int x=x+1,变量 x 未初始化,请记住在 java 中局部变量未初始化为默认值。注意如果它int x=x+1在类范围内也是(),那么它也会给出编译错误,因为没有创建变量。

于 2013-04-04T09:25:00.020 回答
2
int x = x + 1;

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

warning C4700: uninitialized local variable 'x' used`
于 2013-04-04T09:26:42.757 回答
2

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

Java 编程语言是静态类型的,这意味着必须首先声明所有变量才能使用它们。

查看原始数据类型

于 2013-04-04T09:31:11.627 回答
2

由于代码的实际工作方式,该代码行编译时不会出现警告。当您运行代码时,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
于 2013-04-06T09:54:39.690 回答
-1

编译器从右到左读取语句,而我们设计相反。这就是为什么它一开始很生气的原因。养成从右到左阅读语句(代码)的习惯,您就不会有这样的问题。

于 2013-05-03T16:45:56.793 回答