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

Posted

技术标签:

【中文标题】为啥两个程序有前向引用错误而第三个没有?【英文标题】:Why do two programs have forward referencing errors while the third does not?为什么两个程序有前向引用错误而第三个没有? 【发布时间】:2015-08-30 13:02:28 【问题描述】:

以下内容无法编译,给出“非法前向引用”消息:

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 可以?

【问题讨论】:

第三个 sn-p 如果更改为 testString1 = this.testString2; 将起作用 这是我第一次在 Java 类中看到不属于 ctor、静态 ctor 或方法的代码块。很想知道它是什么,它如何通过编译以及何时执行 @sharonbn:它们是实例初始化器或静态初始化器,分别在 JLS 的 8.6 和 8.7 节中描述。​​ 【参考方案1】:

JLS 的 8.3.3 部分对此进行了介绍:

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

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

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

用法不在作业的左侧;

C 是包含使用的最内层类或接口。

使用在使用后以文本形式出现的实例变量的使用有时会受到限制,即使这些实例变量在范围内。具体来说,如果以下所有条件都为真,则为编译时错误:

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

使用是 C 的实例变量初始化器或 C 的实例初始化器中的简单名称;

用法不在作业的左侧;

C 是包含使用的最内层类或接口。

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

如果你把它改成:

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

...那么它不会编译。

或者反方向,你可以把静态初始化块中的代码改成:

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

奇怪,但事情就是这样。

【讨论】:

我来这里是因为你的推文,你得到了我的 +1,但这是@bayou.io 链接到的not new。 @MarkHurd:是的,这对我来说只是新的 :)【参考方案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

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

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

    静态初始化将按照给定的顺序执行。

    非静态变量将被创建但不会被初始化。 将按照给定的顺序执行非静态初始化。

按顺序是指代码中的出现。

我猜这个步骤回答了你的两个不工作示例StaticInitialisationInstanceInitialisation2

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

中,当我调用 InstanceAndSataticInit.testStringStatic 时,static 也会发生同样的情况

【讨论】:

【参考方案3】:

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


    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"?

【讨论】:

【参考方案4】:

这里我们要了解的是,在第二个代码 sn-p 中,您使用的是 block 和 this 关键字

    如果创建了对象,则执行该块。 这意味着对象是在堆区创建的。 您在外部使用 this 关键字来获取实例变量的值。 此处使用默认值创建的对象将作为值返回。 如果不使用此关键字,您也无法编译 2nd sn-p。

【讨论】:

以上是关于为啥两个程序有前向引用错误而第三个没有?的主要内容,如果未能解决你的问题,请参考以下文章

包含命名空间的类模板的转发声明会导致编译错误

如果没有前向引用,对如何创建引用感到困惑?

C++ 简单循环引用和前向声明问题

为啥函数没有得到动态数组? (我正在使用指针和引用)

为啥内联 JavaScript 在引用 JavaScript 错误时起作用 [重复]

前向引用