封闭类中的私有枚举和静态字段

时间:2016-08-17 16:30:14

标签: java enums static

我理解为什么枚举构造函数无法访问静态字段 枚举本身内部和方法,以及为什么允许这样做 在课堂上。例如,以下代码为

import java.util.ArrayList;
import java.util.List;

public enum Foo {
    A("Some string"),
    B("Some other string"),
    ;

    static List<String> list = new ArrayList<>();

    Foo(String description) {
        list.add(description);
    }
}

此代码导致编译时错误illegal reference to static field from initializer

相关背景

在静态字段包含所有内容之前调用枚举构造函数 已初始化。在上面的示例中,这意味着list尚未初始化。这是因为静态字段在文本中初始化 订购(section 12.4.2)

  

接下来,执行类变量初始值设定项和静态   类的初始值设定项,或者字段的初始值设定项   界面,以文本顺序,就像它们是一个单独的块一样。

(强调我的)

因为枚举值本身总是先于任何其他值 字段,包括静态字段,它们不可用 枚举构造函数,即枚举之前不能有静态字段 值AB

问题

然而,这是我的问题,为什么它是一个“私人”(封闭在一个类中)enum 可以访问其封闭类的静态字段, 无论枚举是否出现在---或---之前 在静态字段之后?特别是,在Java规范中指定了哪个?

请参阅以下代码以供参考

import java.util.ArrayList;
import java.util.List;

public class Bar {
    static List<String> first = new ArrayList<>();

    enum Baz {
        A("Some string"),
        B("Some other string"),
        ;


        Baz(String description) {
            // Can access static fields from before the enum
            first.add(description);

            // Can access static fields from _after_ the enum
            second.add(description);
        }
    }

    static List<String> second = new ArrayList<>();
}

2 个答案:

答案 0 :(得分:4)

这在JLS中占有一席之地。 When Initialization Occurs chapter

  

意图是类或接口类型具有一组初始化器   使它处于一致的状态,并且这个状态是第一个   其他类观察到的状态。 static初始值设定项和   类变量初始值设定项以文本顺序执行,但可能不是   引用在声明中声明的类中的类变量   使用后以文本形式出现,即使这些类变量是   在范围内(§8.3.3)。此限制旨在在编译时检测   时间,大多数循环或其他错误的初始化。

该粗体代码段指的是直接包含访问权限的类。

enum类型在Java语言规范here

中定义
  

枚举声明指定一个新的枚举类型,一种特殊的类类型。

您在Baz构造函数

中访问的字段
Baz(String description) {
    // Can access static fields from before the enum
    first.add(description);

    // Can access static fields from _after_ the enum
    second.add(description);
}

不是在类 中声明类变量,即枚举类型Baz。因此允许访问。

我们可以更深入地研究detailed class initialization procedure,这解释了每个类(类,接口,枚举)是独立初始化的。但是,我们可以创建一个看待尚未初始化的值的示例

public class Example {
    public static void main(String[] args) throws Exception {
        new Bar();
    }
}

class Bar {
    static Foo foo = Foo.A;
    static Integer max = 42;

    enum Foo {
        A;

        Foo() {
            System.out.println(max);
        }
    }
}

这将打印nullmax类型的构造函数允许访问enum,尽管我们的程序执行在max初始化之前达到了访问权限。 JLS warns against this

  

初始化代码不受限制的事实允许示例   构造可以观察到类变量值的地方   当它在初始化之前仍然具有其初始默认值时   表达式被评估,但这样的例子在实践中很少见。 (这样   例如,也可以构造示例用于变量初始化   (§12.5)。)Java编程语言的全部功能可用   在这些初始化者中; 程序员必须小心谨慎

您的原始Foo示例引入了一项额外规则,该规则在chapter on Enum Body Declarations

中定义
  

引用枚举类型的静态字段是编译时错误   来自构造函数,实例初始值设定项或实例变量   枚举类型的初始化表达式,除非该字段是a   常数变量(§4.12.4)。

该规则会阻止您的Foo代码段进行编译。

enum constants translate to public static final fields。它们首先在enum类型定义中以文本形式出现,因此首先进行初始化。它们的初始化涉及构造函数。存在的规则是为了防止构造函数看到其他类变量的未初始化值,这些值必须在以后初始化。

答案 1 :(得分:3)

嵌套类的位置没有意义。您可以在声明之前引用嵌套类(在源文本的前面),就像在声明它们之前可以引用方法一样。

这是因为嵌套类实际上是一个独立的类。封闭类和嵌套类是独立初始化的。

因此,我们可以说BarBaz都未初始化。

如果某些代码需要Bar,那么Bar将被初始化。由于Baz未引用Bar,因此Baz不会在此时初始化。

但是,如果某些代码需要Baz(它们都尚未初始化),则Baz初始化开始。当Baz的{​​{1}}构造函数开始运行时,A仍未初始化。然后第一行需要Bar,并开始Bar初始化。 Bar初始化在执行Bar语句之前正常完成。第二个语句也可以执行,因为first.add(description)已完全初始化。

如您所见,没有初始化顺序冲突。 Bar将完全初始化,因此完全可用,供Bar构造函数使用。

为了查看事件的顺序,我添加了一些打印语句:

Baz

输出

public class Test {
    public static void main(String[] args) {
        Bar.Baz x = Bar.Baz.A;
    }
}
class Bar {
    static { System.out.println("Bar initializing..."); }
    static List<String> first = new ArrayList<>();

    enum Baz {
        A("Some string"),
        B("Some other string"),
        ;
        static { System.out.println("Baz initializing..."); }


        Baz(String description) {
            System.out.println(getClass() + "." + name() + " in construction...");
            // Can access static fields from before the enum
            first.add(description);

            // Can access static fields from _after_ the enum
            second.add(description);
            System.out.println(getClass() + "." + name() + " constructed...");
        }
        static { System.out.println("Baz initialized..."); }
    }

    static List<String> second = new ArrayList<>();
    static { System.out.println("Bar initialized"); }
}

如您所见,class Bar$Baz.A in construction... Bar initializing... Bar initialized class Bar$Baz.A constructed... class Bar$Baz.B in construction... class Bar$Baz.B constructed... Baz initializing... Baz initialized... 将在Bar构造函数中开始初始化 ,并且此时将完全初始化。因此,无论源文本位置如何,它都可供Baz完全使用。

您还可以看到Baz静态初始值设定项在之后构建枚举后才会运行,这当然是您无法从构造函数中引用静态成员的原因。