当前位置: 首页 > 知识库问答 >
问题:

这段Java代码为什么要编译?

祁雪峰
2023-03-14

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

int x = x = 1;

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

int x = x + 1;

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

共有3个答案

慕仲渊
2023-03-14

大致相当于:

int x;
x = 1;
x = 1;

首先,int

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

在本例中,表达式是x=1,这也是一条语句x=1是一条有效的语句,因为varx已经声明。它也是一个值为1的表达式,然后将其再次赋给x

齐驰
2023-03-14
int x = x = 1;

是相当于

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

而在

int x = x + 1; 

首先,我们需要计算x1,但x的值未知,因此会出现错误(编译器知道x的值未知)

孟哲
2023-03-14

对于字段,int b=b1是非法的,因为b是对b的非法正向引用。实际上,您可以通过编写intb=this来解决这个问题。b 1,编译过程中没有投诉。

对于局部变量,int d=d 1是非法的,因为d在使用前没有初始化。字段则不是这种情况,它们总是默认初始化的。

通过尝试编译,您可以看到差异

intx=(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的声明无效,并因非法转发引用错误而失败
d的声明无效,并且由于变量d可能未初始化而失败错误。

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

Java中的字段初始化器受JLS§8.3.2字段初始化规范。

JLS§6.3“声明范围”中定义了字段的范围。

相关规则是:

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

§8.3.2.3规定:

只有当成员是类或接口C的实例(分别是静态)字段,并且符合以下所有条件时,成员的声明才需要在使用前以文本形式显示:

  • 这种用法出现在C的实例(分别为静态)变量初始值设定项或C的实例(分别为静态)初始值设定项中。

您实际上可以在声明字段之前引用它们,除非在某些情况下。这些限制旨在防止像

int j = i;
int i = j;

来自编译。Java规范称,“上述限制旨在在编译时捕获循环或其他格式错误的初始化。”

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

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

inta=a=1编译是因为它违反了(b):引用a被分配到,因此在a完整声明之前引用a是合法的。

int b=this. b1也会编译,因为它违反了(c):引用this. b不是一个简单的名称(它用this.来限定)。这个奇怪的构造仍然定义良好,因为this. b的值为零。

因此,基本上,初始化器中对字段引用的限制阻止了inta=a1的成功编译。

注意,字段声明int b=(b=1)b将无法编译,因为最终的b仍然是非法的正向引用。

局部变量声明受JLS§14.4“局部变量声明声明”管辖。

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

  • 块(§14.4)中局部变量声明的范围是声明出现的块的其余部分,从其自身的初始值设定项开始,并包括局部变量声明语句右侧的任何其他声明符

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

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

  • 对于本地变量或空白最终字段x的每一次访问,x必须在访问之前被指定,否则会发生编译时错误。

intd=d1,对d的访问被解析为局部变量fine,但由于在访问d之前未分配d,编译器发出错误。在int c=c=1中,c=1首先发生,分配c,然后将c初始化为该分配的结果(即1)。

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

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

  • 问题内容: 在方法或类范围内,下面的行进行编译(带有警告): 在类范围中, 变量获取其默认值 ,以下给出“未定义引用”错误: 它不是第一个应该以相同的“未定义参考”错误结束吗?还是第二行应该编译?还是我缺少什么? 问题答案: tl; dr 对于 字段 ,是非法的,因为它是对的非法前向引用。您实际上可以通过编写来解决此问题,该文件可以毫无抱怨地进行编译。 对于 局部变量 ,是非法的,因为未在使用前进

  • 我有一些流处理代码,它接受一个单词流并对它们执行一些操作,然后将它们简化为一个,其中包含单词作为键,单词的出现次数作为值。为了代码的简洁性,我使用了jOOL库的类,其中包含许多有用的快捷方法。 类型中的方法不适用于参数 type未定义此处适用的 为什么的行为与有任何不同,我(也许是天真地)认为它是直接等效的,为什么编译器在使用它时不能处理它? (是的,我知道我可以通过将以前的应用程序移到操作中来删

  • 问题内容: 因此,我刚刚意识到反编译Java代码是多么容易。我一直在网上搜索,但似乎无法弄清楚 为什么 这么容易。每次我在Google上搜索“为什么要反编译文件?”之类的信息时,或“为什么Java这么容易反编译”,我所得到的只是指向可以轻松反编译我的代码的软件的链接。因此,我向您介绍StackOverflow:为什么Java可以转换回容易阅读的源代码,而C ++和其他语言对反编译不是很友好? 谢谢

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

  • 我们最近在生产中遇到了一个我觉得非常有趣的错误。我长期以来的理解是Java布尔只能是false或true。然而,在三元运算符中,它似乎最终可以解析为null,而且它从未产生编译错误,并且一直构建到生产。我很惊讶下面的代码没有产生编译错误。有人知道为什么它编译得很好吗?恕我直言,它不应该编译!它最终解析的值是一个本机布尔值。