【问题标题】:Which cases allow forward referencing in static initializer blocks?哪些情况允许在静态初始化块中进行前向引用?
【发布时间】:2017-03-06 13:01:58
【问题描述】:

在 Java 中,静态变量的初始化是按文本顺序执行的。因此,无法访问静态变量,例如在一个静态初始化块被清除之前。

但是,我注意到在声明静态变量之前存在某些可能性。

给定以下代码:

static {
    STATIC = true;
}
public static boolean STATIC = false;

public static void main (String[] args) {
    System.out.println(STATIC);
}

人们会认为静态初始化块 (STATIC = true;) 中存在编译错误,但实际上编译正常。此外,程序的输出是false。我希望它是 true,因为我在静态初始化程序中为其分配了一个值。

  • 为什么这段代码sn -p不是编译错误,为什么初始化后变量没有设置为true?
  • 在哪些情况下(通常)可以在 Java 中转发访问静态变量?

【问题讨论】:

    标签: java class static initialization declaration


    【解决方案1】:

    为什么这段代码sn -p不是编译错误,为什么初始化后变量没有设置为true?

    如果在赋值语句的左侧使用静态变量,Java 允许前向引用。初始化后值为false的原因是,当Java到达静态初始化块时,变量STATIC已经被声明(发生在类的实际初始化之前)并初始设置为false(默认值)。在静态初始化器之后,声明行的赋值仍然被执行,值将结束为false。

    在哪些情况下(通常)可以在 Java 中转发访问静态变量?

    Java 8 的 Java 语言规范 (JLS) 在 12.4.1 When Initialization Occurs 中说:

    静态初始化器和类变量初始化器是 按文本顺序执行,并且可能不引用类变量 在出现 声明的类中声明 使用后的文字,即使这些类变量在 范围(第 8.3.3 节)。

    如前所述,一般来说,前向引用是不允许的。但是8.3.3 Forward References During Field Initialization中描述了某些例外情况:

    8.3.3。字段初始化期间的前向引用

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

    • 在类或接口 C 中声明类变量 使用类变量后以文本形式出现
    • 使用 是 C 的类变量初始值设定项中的简单名称 C的静态初始化器
    • 使用不在左侧 赋值
    • C是最里面的类或接口 封闭用途

    为了说明这些条件,我创建了 ThisClass 类,并为每个条件提供了一个示例。如果这四个条件中只有一个为假,则不是编译错误!


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

    public static boolean STATIC1 = false;
    static {
        if (!STATIC1) {
            System.out.println("Set STATIC1 to true");
            STATIC1 = true;
        }
    }
    

    STATIC1 的使用发生在它被定义之后。违反了编译器错误的最基本条件。如你所料,初始化后STATIC1的值为true。


    2。用法是 C 的类变量初始化器或 C 的静态初始化器中的简单名称

    static {
        if (!ThisClass.STATIC2) {
            System.out.println("Set STATIC2 to true");
            ThisClass.STATIC2 = true;
        }
    }
    public static boolean STATIC2 = false;
    

    现在满足第一个条件,但违反了第二个条件。我们使用限定名(而不是简单名)来访问变量STATIC2。区别在6.2 Names and Identifiers 中的 JLS 中进行了描述:

    名称有两种形式:简单名称和限定名称。
    simple name 是单个标识符。
    qualified name 由一个名称、一个“.”组成。令牌和标识符。

    更有趣的是,初始化期间实际发生了什么。在静态初始化程序的第一行,STATIC2 的值是通过类限定符读取的。此时(因为类是按文本顺序初始化的)变量STATIC2 尚未初始化。在这种情况下,使用默认值,对于boolean,默认值是false。输入if 块并将STATIC2 的值设置为true。继续初始化,STATIC2 再次设置为false,这是变量初始化后的状态。

    注意:为了证明对true 的赋值确实发生了,你可以尝试这样的事情:

    static {
        if (!ThisClass.STATIC2a) {
            System.out.println("Set STATIC2a to true");
            ThisClass.STATIC2a = true;
        }
    }
    public static boolean STATIC2a;
    

    这段代码编译得很好,初始化结束时STATIC2a的值将是true!最后一行代码甚至可以替换为以下奇怪的语句,它更清楚地说明了正在发生的事情:

    public static boolean STATIC2a = ThisClass.STATIC2a;
    

    3.使用不在作业的左侧

    static {
        System.out.println("Set STATIC3 to true");
        STATIC3 = true;
    }
    public static boolean STATIC3 = false;
    

    现在我们在声明之前使用类变量,并且使用一个简单的名称。但是,不满足 JLS 中的第三个条件,因为它出现在作业的左侧。发生的事情与访问合格的情况大致相同。 STATIC3 的默认值 false 被 true 覆盖,然后在最后一行代码中设置回 false。

    注意: 与示例 2a 相同。如果最后一行代码没有赋值(或者STATIC3a = ThisClass.STATIC3a无操作),STATIC3a被初始化为true:

    static {
        System.out.println("Set STATIC3a to true");
        STATIC3a = true;
    }
    public static boolean STATIC3a;
    

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

    static class OtherClass {
        static {
            if (!STATIC4) {
                System.out.println("Set STATIC4 to true");
                STATIC4 = true;
            }
        }
    }
    public static boolean STATIC4 = false;
    

    STATIC4 定义在外部类ThisClass 中,因此从OtherClass 的使用角度来看,没有定义在最内部的类中。静态块中的第一行满足上述所有三个条件。声明在使用后以文本形式出现,它由一个简单的名称引用,并且使用也不在赋值的左侧。但由于外部类总是在任何嵌套类之前初始化,ThisClass 中的STATIC4 = false 赋值发生在OtherClass 中的静态初始化之前。

    重要的是要看到,OtherClass 的初始化不必在外部 ThisClass 初始化时发生(它甚至可能被加载,但正在加载!= 初始化)。我们需要确保 OtherClass 由例如初始化。创建它的一个实例(参见 JLS 中的12.4.1)。


    整个程序可以在这里学习: http://ideone.com/fl5Vet

    【讨论】:

    • 很好的解释,但我仍然不明白限制简单名称并接受限定名称的意义何在?也许它就像类型套管:明确指出你的代码做了什么。
    • 我也不知道确切的原因。你的解释听起来很合理。这可能是一种预期的故障保护,以防止意外的前向引用被最下面的语句覆盖。
    • 关于通过限定名的前向引用还有另一个有趣的时刻:对静态变量的引用返回默认值(0、false、null 或 \u0000),但对静态最终变量(常量)的引用返回实际值(类似于普通参考)。
    • 这是因为常量在比静态类变量更早的阶段被初始化。在静态初始化程序使用前向引用运行时,类变量尚未初始化,因此使用默认值。这也是示例 2 中的 !ThisClass.STATIC2 始终为 false 的原因,即使最后一行的初始化是 true。
    猜你喜欢
    • 2017-12-07
    • 1970-01-01
    • 2019-05-25
    • 1970-01-01
    • 2011-01-26
    • 1970-01-01
    • 1970-01-01
    • 2011-01-12
    • 1970-01-01
    相关资源
    最近更新 更多