繁体   English   中英

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

[英]Why does this Java code compile?

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

int x = x = 1;

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

int x = x + 1;

是不是第一个x = x = 1应该以相同的'undefined reference'错误结束? 或者第二行int x = x + 1应该编译? 或者有些东西我不见了?

TL;博士

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

相关规则是:

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

§8.3.2.3说:

成员声明只有在成员是类或接口C的实例(分别为静态)字段并且满足以下所有条件时才需要以文本方式显示:

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

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

int j = i;
int i = j;

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

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

总之,这些规则基本上是说,你必须提前到字段的引用的声明场如果(a)的引用是一个初始化,(B)引用不会被分配到,(c)证明是简单的名称 (没有像this.限定符this. )和(d)它不是从内部类中访问的。 因此,满足所有四个条件的前向引用是非法的,但是至少在一个条件上失败的前向引用是可以的。

int a = a = 1; 编译因为它违反了(B):参考a 分配给了,所以它的法律是指a提前a的完整的声明。

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编译器检查循环和分支以确保在任何使用之前总是发生赋值(这就是为什么明确赋值具有专用的整个规范部分的原因)它)。 基本规则是:

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

int d = d + 1; 之后,进入d解析为局部变量的罚款,但因为d尚未分配前d被访问时,编译器会发出一个错误。 int c = c = 1 ,首先发生c = 1 ,它分配c ,然后将c初始化为该赋值的结果(即1)。

注意,由于明确的赋值规则,局部变量声明int d = (d = 1) + d; 成功编译( 字段声明int b = (b = 1) + b )不同,因为d在到达最后一个d时明确赋值。

int x = x = 1;

相当于

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

而在

int x = x + 1; 

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

它大致相当于:

int x;
x = 1;
x = 1;

首先, int <var> = <expression>; 总是相当于

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

在这种情况下,表达式是x = 1 ,这也是一个语句。 x = 1是一个有效的语句,因为已经声明了var x 它也是值为1的表达式,然后再将其赋值给x

在java或任何现代语言中,赋值来自右侧。

假设你有两个变量x和y,

int z = x = y = 5;

此语句有效,这是编译器分割它们的方式。

y = 5;
x = y;
z = x; // which will be 5

但在你的情况下

int x = x + 1;

编译器给出了一个异常,因为它会像这样分裂。

x = 1; // oops, it isn't declared because assignment comes from the right.

int x = x = 1; 不等于:

int x;
x = 1;
x = x;

javap再次帮助我们,这些是为此代码生成的JVM指令:

0: iconst_1    //load constant to stack
1: dup         //duplicate it
2: istore_1    //set x to constant
3: istore_1    //set x to constant

更像:

int x = 1;
x = 1;

这里没有理由抛出未定义的引用错误。 现在在初始化之前使用变量,因此该代码完全符合规范。 实际上根本没有使用变量 ,只是赋值。 而且JIT编译器会更进一步,它将消除这种结构。 说实话,我不明白这段代码是如何连接到JLS的变量初始化和使用规范的。 没用就没问题。 ;)

如果我错了,请更正。 我无法弄清楚为什么其他答案,涉及许多JLS段落收集了这么多的优点。 这些段落与本案没有任何共同之处。 只是两个串行任务,而不是更多。

如果我们写:

int b, c, d, e, f;
int a = b = c = d = e = f = 5;

等于:

f = 5
e = 5
d = 5
c = 5
b = 5
a = 5

最正确的表达式只是逐个分配给变量,没有任何递归。 我们可以以任何方式混淆变量:

a = b = c = f = e = d = a = a = a = a = a = e = f = 5;

int x = x + 1; 你加1,X,那么什么是价值x ,它尚未创建。

但是在int x=x=1; 将编译没有错误,因为您为x分配1。

您的第一段代码包含第二个=而不是加号。 这将在任何地方编译,而第二段代码不会在任何一个地方编译。

在第二段代码中,x在其声明之前使用,而在第一段代码中,它只被分配两次,这没有意义但是有效。

让我们一步一步地分解,正确的联想

int x = x = 1

x = 1 ,将1赋值给变量x

int x = x ,将int x = x指定为自身,作为int。 由于x先前被指定为1,因此它保留1,尽管是多余的方式。

编译好。

int x = x + 1

x + 1 ,将一个加到变量x。 但是,x未定义,这将导致编译错误。

int x = x + 1 ,因此这行编译错误,因为equals的右边部分将不编译,将一个添加到未分配的变量

第二个int x=x=1是编译因为你将值赋给x但是在其他情况下int x=x+1这里变量x没有初始化,记住java局部变量没有初始化为默认值。 注意如果它在类范围内是( int x=x+1 ),那么它也会因为没有创建变量而给出编译错误。

int x = x + 1;

在Visual Studio 2008中成功编译并发出警告

warning C4700: uninitialized local variable 'x' used`

x未在x = x + 1初始化;。

Java编程语言是静态类型的,这意味着必须首先声明所有变量才能使用它们。

查看原始数据类型

由于代码的实际工作方式,代码行不会带有警告进行编译。 当您运行代码int x = x = 1 ,Java首先根据定义创建变量x 然后它运行赋值代码( x = 1 )。 由于x已经定义,系统没有错误,将x设置为1.这将返回值1,因为现在是x的值。 因此, x现在最终设置为1。
Java基本上执行代码,就像这样:

int x;
x = (x = 1); // (x = 1) returns 1 so there is no error

但是,在你的第二段代码中, int x = x + 1+ 1语句需要定义x到那时它不是。 由于赋值语句总是意味着首先运行=右边的代码,因此代码将失败,因为x未定义。 Java会像这样运行代码:

int x;
x = x + 1; // this line causes the error because `x` is undefined

编译器从右到左阅读语句,我们的目的是做相反的事情。 这就是为什么它一开始很恼火。 从右到左让这个习惯读取语句(代码),你就不会遇到这样的问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM