当前位置: 首页 > 面试题库 >

为什么要编译此Java代码?

米俊喆
2023-03-14
问题内容

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

int x = x = 1;

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

int x = x + 1;

它不是第一个x = x = 1应该以相同的“未定义参考”错误结束吗?还是第二行int x = x + 1应该编译?还是我缺少什么?


问题答案:

tl; dr

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

相关规则是:

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

第8.3.2.3节说:

仅当成员的声明是类或接口C的实例(分别为静态)字段且满足以下所有条件时,才需要使用文本形式的成员声明:

  • 使用情况发生在C的实例(分别为静态)变量初始化程序或C的实例(分别为静态)变量初始化程序中。
  • 用法不在作业的左侧。
  • 用法是通过一个简单的名称。
  • C是包含用法的最里面的类或接口。

实际上,在某些情况下,您可以在声明字段之前先对其进行引用。这些限制旨在防止类似

int j = i;
int i = j;

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

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

简而言之,规则基本上说,如果(a)引用位于初始化程序中,(b)引用未分配给该字段,(c)引用为a ,则 必须 在对该字段的引用之前声明一个字段
简单名称
(没有类似的限定词this.)和(d)不能从内部类中访问它。因此,满足所有四个条件的前向引用是非法的,但是至少在一个条件下失败的前向引用是可以的。

int a = a = 1;编译是因为它违反了(b):引用a 分配给它,因此aa的完整声明之前进行引用 合法的。

int b = this.b + 1还会编译,因为它违反了(c):引用this.b不是一个简单的名称(已使用限定this.)。这个奇数构造仍然是完美定义的,因为this.b它的值为零。

因此,基本上,初始化器中对字段引用的限制会阻止int a = a + 1成功进行编译。

观察到字段声明int b = (b = 1) + b无法 编译,因为最终声明b仍然是非法的前向引用。

局部变量

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

局部变量的 范围
在JLS§6.3声明的范围中定义:

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

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

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

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

在中int d = d + 1;,对的访问d被解析为局部变量fi​​ne,但是由于d之前未分配访问权限d,因此编译器会发出错误。在中int c = c = 1c = 1首先发生分配的c,然后c初始化为该分配的结果(为1)。

请注意,由于有明确的赋值规则,局部变量声明int d = (d = 1) + d; 成功编译( field声明 不同int b = (b = 1) + b),因为dd到达最终变量时已明确赋值。



 类似资料:
  • 在方法或类范围内,下面的行编译(带有警告): 在类作用域中,变量获取其默认值,以下给出未定义引用错误: 这难道不是第一个应该以相同的未定义引用错误结束吗?或者第二行应该编译?或者我错过了什么?

  • 问题内容: 为什么要编译Python脚本?您可以直接从.py文件运行它们,并且效果很好,那么在性能上有什么优势吗? 我还注意到,我的应用程序中的某些文件被编译为.pyc,而另一些则没有,为什么? 问题答案: 它被编译为字节码,可以更快,更快速地使用。 无法编译某些文件的原因是,每次运行脚本时都会重新编译与之一起调用的主脚本。所有导入的脚本将被编译并存储在磁盘上。 Ben Blank的 重要补充:

  • 问题内容: 这段代码使我凝视了几分钟: 我以前从未见过,而且我也不知道Java有一个“ loop”关键字(NetBeans甚至没有像关键字一样给它上色),并且它在JDK 6中可以很好地编译。 有什么解释? 问题答案: 这不是一个。 用法:

  • 问题内容: 以下Java代码无法编译: 编译器报告: 奇怪的是,标记为“ OK”的行可以正常编译,但是标记为“ Error”的行失败。它们看起来基本相同。 问题答案: 您的lambda需要与保持一致。如果您参考JLS#15.27.3(Lambda的类型): 如果满足以下所有条件,则lambda表达式与函数类型一致: […] 如果函数类型的结果为void,则lambda主体为语句表达式(第14.8节

  • 我很难理解为什么要编译以下代码: 我可以理解为什么第一个赋值是有效的-

  • 问题内容: 该程序在Java 7中(或在Java 8中带有)可以很好地编译,但是在Java 8中无法编译: 结果: 换句话说,这是Java 7和8之间的 反向 源不兼容。我已经遍历了Java SE 8和Java SE 7 列表之间的不兼容性,但是没有找到任何适合我的问题的东西。 那么,这是一个错误吗? 环境: 问题答案: 感谢您的报告。这看起来像个错误。我会照顾好它,并且一旦我们有更多关于为什么发