为什么在使用带有类名的静态变量时未显示非法的前向引用错误

在下面的代码中访问具有类名的静态变量时,它不会抛出前向引用错误但是在没有类名的情况下访问它。 为什么在使用类名访问时不会发生这种情况?

class Test{ static { System.out.println(a); // shows error a = 99; // and this line too doesn't give error System.out.println(Test.a); // this line doesn't } static int a = 10; static{ System.out.println(a); } } 

前向引用的规则在JLS§8.3.3中定义:

使用声明在使用后以文本forms出现的类变量有时会受到限制,即使这些类变量在范围内(第6.3节)。 具体来说,如果满足以下所有条件,则为编译时错误:

  • 在使用类变量之后,类或接口C中的类变量声明以文本forms出现;

  • 在C的类变量初始化器或C的静态初始化器中使用是一个简单的名称;

  • 使用不在作业的左侧;

  • C是封闭使用的最内层类或接口。

所以,基本上你的第一个Sysout()满足所有上述4个条件,因此它是编译时错误。

在第二个Sysout() ,您正在访问使用它的限定名称,而不是简单名称,根据上述规则允许使用。

现在,原因是,当您访问Test.a ,编译器确保已加载Test类并且已初始化所有static字段,因此它可以访问字段a 。 但是在访问简单名称时,编译器不确定a初始化程序是否已经运行,因为它可能仍在处理加载类。

考虑以下加载类的过程:

  • 加载类时,将为其中声明的所有static变量分配内存。 此时,变量a已分配其内存(声明已完成)
  • 然后所有static初始化程序按发生顺序运行。
    • 第一个陈述是Sysout(a);a尚未初始化,因此您无法访问它。 (错误)
    • 第二个陈述是a = 99 。 在这里,您实际上是在初始化变量a 。 非常好。
    • 第三个是Sysout(Test.a) – 对此的推理已在上面发布。 编译器知道Test已经加载。
    • 然后执行static int a = 10 。 它重新初始化为10 。 请记住,声明部分已经在第一步处理。

好吧,静态块

 static { System.out.println(a); a = 99; System.out.println(Test.a); } 

将在声明之前执行。

Test.a的行不会触发任何错误,因为编译器会检查并查找在类Test声明的静态变量。

类初始值设定项在加载类时运行,您无法确定何时发生这种情况。 在类初始化程序中运行的代码应仅用于需要处理或“初始化”其他静态方法(或类本身)才能正常工作的内容。

此外,您正在引用尚未声明的变量。 在类初始值设定项(第一个静态块)中,您指定a = 99 ,但尚未声明。 如果你想要声明变量,然后在静态块中初始化它。

没有理由对您要发布的代码类型使用类初始值设定项。 如果有的话,这应该是一个静态方法。

这是一个例子

 class Test{ static int a = 10; static void doSomething(){ System.out.println(a); a = 99; System.out.println(a); } } 

然后在main中你可以调用Test.doSomething();

首先,让我们看看JLS对非法前向引用的看法。

使用声明在使用后以文本forms出现的类变量有时会受到限制,即使这些类变量在范围内(第6.3节)。 具体来说,如果满足以下所有条件,则为编译时错误:

在使用类变量之后,类或接口C中的类变量声明以文本forms出现;

在C的类变量初始化器或C的静态初始化器中使用是一个简单的名称;

使用不在作业的左侧;

C是封闭使用的最内层类或接口。

使用在使用后以声明方式显示声明的实例变量有时会受到限制,即使这些实例变量在范围内也是如此。 具体来说,如果满足以下所有条件,则为编译时错误:

在使用实例变量之后,类或接口C中的实例变量的声明以文本forms出现;

在C的实例变量初始值设定项或C的实例初始值设定项中使用是一个简单的名称;

使用不在作业的左侧;

C是封闭使用的最内层类或接口。

它定义了每个静态和实例变量的非法前向引用。 但是,两者的定义似乎相同。 由于您的问题是关于静态的,我们将深入研究这一点。

1)对于静态变量,请参见declarationinitialization

  static int a; //only declaration static int b = 10; //both declaration and initialization 

2)使用类变量,其声明在使用后以文本forms出现有时受到限制,即使这些类变量在范围内。

它给我们解释了什么? 它说有些情况下我们可以使用静态变量,即使我们之后声明它们。 在某些情况下,这是不允许的。 那么,这是不允许的情况是什么?

如果以下4个条件同时为真。 此外,即使您事后声明它,您也可以自由使用它。

 a) The declaration of a class variable in a class or interface C appears textually after a use of the class variable; b) The use is a simple name in either a class variable initializer of C or a static initializer of C; c) The use is not on the left hand side of an assignment; d) C is the innermost class or interface enclosing the use. 

嗯, a)点很简单。 它表示只有在你想要使用静态变量之前必须查看这些规则,然后才会知道为什么要深入研究这个JLS。

b)要点是,如果你使用简单的名字,比如boy [而不是像MyClass.boy那样附加类名,那么你might输入非法前向引用的问题,否则你就是我的朋友。 但是这个条件不符合非法前向引用的条件,否则你a=99代码中的a=99会立即给我们错误。 要生成此错误,还有2个条件必须成立。 如果以下2不符合条件,您可以像这样使用它。]

c)点非常简单。 你没有在任务的左手边使用? 如果我们看a=99 ,不要! System.out.println(a) – 这甚至不是一项任务。 因此,没有左手分配是真的。

d)点也很简单。 它只是告诉你他在定义中指的是哪个类/接口C.你的C =测试。

现在让我们重新审视您的代码。 在每行代码中,我将评论True+False+True+false这样的意思是对于每一行,a),b),c),d)将给予每一行。 好的 ?

 class Test { static { System.out.println(a); // True + True + True +True ; Illegal forward reference a = 99; // True + True + False +True ; No illegal forward reference System.out.println(Test.a); // True + False + True + True No illegal forward reference } static int a = 10; static { System.out.println(a); } } 

人们可能会想到的下一个问题是它现在会印刷什么? 如果我们在宣布它之前使用它将需要什么价值?

现在我们来看静态初始化的规则。 在这里,我们将根据您的代码进行操作。

当加载类并且没有非法的前向引用时,所有静态变量都已初始化为默认值并存在于内存中。

现在,在进行静态初始化之后。

 class Test { static { System.out.println(Test.a); // prints 0 a = 99; // a get 99 System.out.println(Test.a); // prints 99 } static int a = 10; static { System.out.println(a); // prints 10 } public static void main(String[] args) { } }