为什么int p =(p = 1)+ p; 字段定义中失败,但是方法内可以吗?

Cod*_*ete 8 java

class Test{

    int p = (p=1) + p;   // ERR "Cannot reference a field before it is defined"
    int q = (q=1) + this.q; //fine!

    void f() {
        int t = (t=1) + t; // fine!
    }       
}
Run Code Online (Sandbox Code Playgroud)

在第一种情况下,我了解:执行赋值(或后续加法?)时,p被视为未声明。

但是,为什么一个方法内有不同呢?OK t不会被视为未初始化,因为(t = 1)在加法之前执行。好的,t不是一个字段,但是目前还没有声明!

我能以某种方式理解吗?还是我会记住这种差异?

也许这也与以下内容有关:

    static int x = Test.x + (x=1) + Test.x; // produces 2

    void f() {
       int y = y + (y=1) + y;  // ERR  local variable y may not have been initialized
   }
Run Code Online (Sandbox Code Playgroud)

为什么是2?首先以某种方式评估(x = 1)(未声明x!),然后返回1,现在x已分配(!?)并包含1,因此两个Test.x均为1,但是(x = 1 )运算子也传回1,因此运算结果必须是1 +1 + 1,而3必须(赋值)到x中作为Test.x + (x=1) + Test.x运算式。

部分答案:实际上,结果是特定于实现的。JLS仅保证对二进制运算符的操作数求值的顺序(从左到右)。但是,如果我们拥有具有相同优先级的二元运算符(例如加号),则不能保证它们的求值顺序。在我的情况下,加上运算符首先在最左边计算,这就是为什么静态“ int x = Test.x(零)+(x = 1)+ Test.x(在(x = 1)之后为IS 1);” 是0 + 1 + 1(请记住,x = 1是返回分配值的运算符)。同样在我的情况下,方法是“ int y = y +(y = 1)+ y;” 最左边的plus运算符首先被评估(给出错误),但是如果JVM选择首先评估第二个加号运算符,那么可以保证首先评估其左操作数,并且(y = 1)将初始化y变量(因此代码可以编译!)

我仍然不确定为什么(x = 1)不会被视为未声明的字段。我模糊地记得,JLS在LHS中允许未声明的变量(因此任何赋值都可行),但在RHS中则不允许(x ++,int sth = x)。我可以使用以下代码段来记住它:

class Test {

    { x = 7; }  // fine! Initializer is like a regular method
    int x;

    static { y = 7; }  // fine! Initializer is like a regular method
    static int y;
Run Code Online (Sandbox Code Playgroud)

PS这肯定不是Java默认值和初始化的重复-那里没有直接的解释。在这里,我们不仅需要默认值(对于int为零)规则,而且还需要非常复杂的组合中的许多不同规则(运算符优先级,尤其是赋值的某些罕见特性!)。我也知道分配优先级最低,分配是一个运算符,它返回值!

小智 1

阅读Java 语言规范中的局部变量声明范围。例 6.3-2 中描述了您的具体问题。描述是这样的:

下面的程序会导致编译时错误,因为局部变量的初始化p在局部变量声明的范围内p,但局部变量p还没有值,无法使用。

  • 另一个参考:[8.3.3。初始化器中字段引用的限制](https://docs.oracle.com/javase/specs/jls/se12/html/jls-8.html#jls-8.3.3)“*通过简单名称引用在类 C 中声明的实例变量 f,如果出现以下情况,则为编译时错误:...引用出现在 f 自己的声明符的初始值设定项中*"" (4认同)