为什么两个程序有前向引用错误而第三个没有?

时间:2015-06-15 06:02:50

标签: java static instance forward

以下内容无法编译,提供“非法转发参考”消息:

class StaticInitialisation {

    static
    {
        System.out.println("Test string is: " + testString);
    }

    private static String testString;

    public static void main(String args[]) {
        new StaticInitialisation();
    }
}

但是,以下编译:

class InstanceInitialisation1 {

    {
        System.out.println("Test string is: " + this.testString);
    }

    private String testString;

    public static void main(String args[]) {
        new InstanceInitialisation1();
    }
}

但以下内容未编译,提供“非法转发参考”消息:

class InstanceInitialisation2 {

        private String testString1;

    {
        testString1 = testString2;
    }

    private String testString2;

    public static void main(String args[]) {
        new InstanceInitialisation2();
    }
}

为什么StaticInitialisation和InstanceInitialisation2不能编译,而InstanceInitialisation1呢?

4 个答案:

答案 0 :(得分:57)

这由JLS的8.3.3部分涵盖:

  

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

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

  •   
  • 在C的类变量初始值设定项或C的静态初始值设定项中,use是一个简单的名称;

  •   
  • 使用不在作业的左侧;

  •   
  • C是封闭使用的最里面的类或接口。

  •   
     

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

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

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

  •   
  • 使用不在作业的左侧;

  •   
  • C是封闭使用的最里面的类或接口。

  •   

在第二种情况下,使用不是一个简单的名称 - 你明确地得到了this。这意味着它不符合上面引用的第二个列表中的第二个项目符号,因此没有错误。

如果您将其更改为:

System.out.println("Test string is: " + testString);

...然后就不会编译了。

或者在相反方向,您可以将静态初始化程序块中的代码更改为:

System.out.println("Test string is: " + StaticInitialisation.testString);

奇怪,但这就是它的方式。

答案 1 :(得分:2)

让我们看看这两个例子,我想这会让你清楚。

public class InstanceAndSataticInit {

    {
        System.out.println("Test string is (instance init): " + this.testString);
    }

    static{
        System.out.println("Test string is (static init ): " + InstanceAndSataticInit.testStringStatic);
    }

    public  static String testStringStatic="test";
    public  String testString="test";

    public static void main(String args[]) {
        new InstanceAndSataticInit();
    }

}

输出:

Test string is (static init ): null
Test string is (instance init): null

并且

public class InstanceAndSataticInitVariableFirst {

    public  static String testStringStatic="test";
    public  String testString="test";

    {
        System.out.println("Test string is (instance init): " + this.testString);
    }

    static{
        System.out.println("Test string is (static init ): " + InstanceAndSataticInitVariableFirst.testStringStatic);
    }



    public static void main(String args[]) {
        new InstanceAndSataticInitVariableFirst();
    }


}

输出:

Test string is (static init ): test
Test string is (instance init): test

所以你可以说序列是这样的。

  1. 将创建静态变量但不会初始化。

  2. 静态初始化将根据给定的顺序执行。

  3. 将创建非静态变量但不会初始化。
  4. 非静态初始化将根据给定的顺序执行。
  5. 按顺序我的意思是在代码中出现。

    我想这些步骤可以回答您的两个无效示例 StaticInitialisation InstanceInitialisation2

    但是,如果您的第二个工作示例 InstanceInitialisation1 使用this关键字,那么您实际上是在帮助编译器忽略文本层次结构。当我在第一个例子中呼叫static InstanceAndSataticInit

    InstanceAndSataticInit.testStringStatic发生同样的事情

答案 2 :(得分:2)

原因很简单 - 分析和禁止所有前向引用都太昂贵或不可能。 e.g。

{
    print( getX();  );    // this.x
    print( that().x );    // this.x
}

int x;
int getX(){ return x; }

This that(){ return this; }

该规范决定禁止一些表明常见程序员错误的简单案例。

另见Recursive initializer works when I add "this"?

答案 3 :(得分:1)

我们必须了解的是,在第二个代码段中,您正在使用块和此关键字

  1. 如果创建了对象,则执行该块。
  2. 这意味着对象是在堆区域中创建的。
  3. 您在外部使用此关键字来获取实例变量的值。
  4. 此处使用默认值创建的对象将作为值返回。
  5. 如果不使用此关键字,您也无法编译第二个代码段。