【问题标题】:Why does int p = (p=1) + p; fail in field definitions but is OK within method?为什么 int p = (p=1) + p;在字段定义中失败但在方法中可以吗?
【发布时间】:2019-11-21 11:18:28
【问题描述】:
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!
    }       
}

在第一种情况下,我理解:执行赋值(或后续添加?)时,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
   }

为什么是 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 只保证二元运算符的操作数被计算的顺序(从左到右)。但是,如果我们有 具有相同优先级的二元运算符(例如,加号),则不能保证它们的评估顺序。 在我的情况下,加号运算符首先从最左边进行评估,这就是为什么 static "int x = Test.x (ZERO) + (x=1) + Test.x (IS 1 after (x=1));"是 0 + 1 + 1(请记住,x=1 是返回赋值的运算符)。 同样在我的情况下,方法“int y = y + (y=1) + y;”首先计算最左边的加号运算符(给出错误),但如果 JVM 选择先计算第二个加号运算符,则保证首先计算其左操作数,并且 (y=1) 将使 y 变量初始化(因此代码会编译!)

我仍然不确定为什么 (x=1) 不被视为未声明的字段。我依稀记得 JLS 允许在 LHS 中未声明的变量(因此任何赋值都有效),但在 RHS 中不允许(x++, int sth=x)。我可以使用以下 sn-p 记住它: p>

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;

附注这肯定不是Default Values and Initialization in Java 的复制品——那里没有直接的解释。在这里,我们不仅需要默认值(int 为零)规则,还需要非常复杂组合中的许多不同规则(运算符优先级,尤其是一些罕见的赋值特性!)。我也知道这里的赋值优先级最低,赋值是一个运算符,它返回值!

【问题讨论】:

  • 记住了吗?不,最好避免两者,只使用它应该具有的值
  • 这是一个面试问题...所以对我来说这很实用。我保证,我永远不会写这样的代码 :) 我不是疯子。
  • 我添加了带有this. 的示例,它可以让您的场景编译。希望您不要介意,这会让其他人找到正确的答案。
  • 讨厌这样的面试问题;通常他们试图成为“聪明”的人,在这种情况下(如果可以的话)我绝对证明他们不是(读到他们是白痴)。不要接受这个提议——即使你得到一个,恕我直言。

标签: java


【解决方案1】:

阅读Java Language Specification 中局部变量声明的范围。示例 6.3-2 中描述了您的确切问题。说明是这样的:

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

【讨论】:

  • p 不是局部变量。
  • 您提供的 JLS 示例是关于字段和局部变量的冲突。此外,它表明 int x = x;是方法中的编译错误,但在我的情况下(稍作修改)代码编译正常!
  • 另一个参考:8.3.3. Restrictions on Field References in Initializers"对于在类 C 中声明的实例变量 f 的简单名称的引用,如果:... 引用出现在f 自己的声明符的初始化器""
  • @CarlosHeuberger 您应该将其发布为答案,但即使 JLS 解释了它,我也不知道为什么内部方法必须不同。
  • @carlos 给了我们一个不允许的规则,我认为 OP(和我)想了解为什么存在这种限制
【解决方案2】:

也许我不会很详细,但我会尝试一下,您指出了 Java 中变量生命周期的非常好的示例。

int p = (p=1) + p;   // ERR "Cannot reference a field before it is defined"

在这种情况下,p 是一个类字段,当编译器加载一个类 p 时尚未初始化(第一次扫描一个类,因此 p 尚未加载到内存中并且无法评估)。

void f() {
        int t = (t=1) + t; // fine!
}

在这种情况下,编译器只加载函数的定义,不管里面是什么(我的意思是如果没有语法错误,也没有每个 IDE 可以检查的那种错误是好的)。这可能是一个奇怪的声明,但没关系,在您调用函数并初始化 t 内联之前不会对其进行评估。

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

在这种情况下,x 是一个静态变量,静态“事物”在类之前加载,因此您可以想象您的编译器将您编写的 x 字段放在所有内容之上。在这一行中,您说 x 等于 1,因此 1 + 1 = 2。这就像做这样的事情

static int x = 1;
x = Test.x + Test.x;

【讨论】:

  • 但是为什么static int x = Test.x + (x=1) + x; 不能编译(假设它在Test 类中)?
  • int p = (p=1) + this.p 是否编译?
  • 这是一个不同的场景,在这种情况下等价于 static int x = 1 + x; x+=测试.x;第一条语句会给你错误。在第一个语句中,最右边的 x 尚未初始化。对于静态字段声明和初始化是内联的,并在第一次扫描时由编译器生成。
  • 为什么,和你写的一模一样,只是字段引用不合格(xTest.x是同一个类变量)
  • 是的,但是有区别,Test.x 是对静态字段 x 的引用,这就是为什么它不会给您错误的原因,Test.x 在 x 被初始化并且它之后被评估编译是因为 Test 有一个静态字段 x
猜你喜欢
  • 2019-11-17
  • 2017-12-16
  • 2022-07-23
  • 1970-01-01
  • 1970-01-01
  • 2019-11-06
  • 1970-01-01
  • 1970-01-01
  • 2021-01-05
相关资源
最近更新 更多