为什么这个Java代码会编译?

2022-08-31 10:44:42

在方法或类作用域中,编译以下行(带有警告):

int x = x = 1;

在类作用域中,变量获取其默认值,下面给出“未定义的引用”错误:

int x = x + 1;

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


答案 1

tl;博士

对于字段,是非法的,因为 是对 的非法转发引用。您实际上可以通过编写来解决此问题,该编译无需投诉。int b = b + 1bbint b = this.b + 1

对于局部变量,是非法的,因为在使用前未初始化。对于始终默认初始化的字段,情况并非如此。int d = d + 1d

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

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;
    }
}

的声明无效,并且失败并显示错误。
的声明无效,并且失败并显示错误。billegal forward referencedvariable d might not have been initialized

这些错误不同的事实应该暗示错误的原因也不同。

领域

Java 中的字段初始值设定项由 JLS §8.3.2 字段初始化控制。

字段的作用域JLS §6.3 声明的作用域中定义。

相关规则有:

  • 在类 C 中声明或由类 C (§8.1.6) 继承的成员的声明的作用域是 C 的整个主体,包括任何嵌套的类型声明。m
  • 实例变量的初始化表达式可以使用类中声明或由类继承的任何静态变量的简单名称,即使是其声明稍后文本发生的变量。
  • 使用其声明在使用后以文本方式显示的实例变量有时会受到限制,即使这些实例变量在作用域内也是如此。请参阅 §8.3.2.3,了解管理对实例变量的前向引用的确切规则。

§8.3.2.3 规定:

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

  • 用法发生在 C 的实例(分别是静态)变量初始值设定项或 C 的实例(分别是静态)初始值设定项中。
  • 用法不在作业的左侧。
  • 用法是通过一个简单的名称。
  • C 是包含用法的最内层类或接口。

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

int j = i;
int i = j;

从编译。Java规范说:“上述限制旨在捕获编译时循环或其他格式错误的初始化。

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

简而言之,规则基本上说,如果 (a) 引用位于初始值设定项中,(b) 引用未赋值到,(c) 引用是简单名称(没有像这样的限定符),并且 (d) 未从内部类中访问,则必须在引用该字段之前声明该字段。因此,满足所有四个条件的前向引用是非法的,但是在至少一个条件下失败的前向引用是可以的。this.

int a = a = 1;编译,因为它违反了 (b):引用被赋值给,因此在 的完整声明之前引用是合法的。aaa

int b = this.b + 1也编译,因为它违反了 (c):引用不是一个简单的名称(它用 ) 限定。这个奇怪的构造仍然是完全明确的,因为其值为零。this.bthis.this.b

因此,基本上,初始值设定项中对字段引用的限制会阻止成功编译。int a = a + 1

请注意,字段声明将无法编译,因为 final 仍然是非法的前向引用。int b = (b = 1) + bb

局部变量

局部变量声明受 JLS §14.4 局部变量声明语句的约束。

局部变量的作用域JLS §6.3 声明的作用域中定义:

  • 块中局部变量声明的作用域 (§14.4) 是声明所在的块的其余部分,从其自己的初始值设定项开始,并在局部变量声明语句中包括右侧的任何其他声明符。

请注意,初始值设定项在所声明的变量的范围内。那么为什么不编译呢?int d = d + 1;

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

  • 对于局部变量或空白最终字段的每次访问,必须在访问之前明确分配,否则会发生编译时错误。xx

在 中,对 的访问解析为局部变量 fine,但由于在访问之前尚未分配,编译器会发出错误。在 中,首先发生,它赋值 ,然后初始化为该赋值的结果(即 1)。int d = d + 1;dddint c = c = 1c = 1cc

请注意,由于明确的赋值规则,局部变量声明成功编译(与字段声明不同),因为在到达最终值时肯定会被赋值。int d = (d = 1) + d;int b = (b = 1) + bdd


答案 2
int x = x = 1;

等效于

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

而在

int x = x + 1; 

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


推荐