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

我明白为什么枚举构造函数不能访问枚举本身中的静态字段和方法,以及为什么类中允许使用静态字段和方法。 以下面的代码为例,

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尚未初始化。 这是因为静态字段是按照文本顺序初始化的(第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<>();
}

这在JLS的地方有点儿。 章初始状态发生时

目的是一个类或接口类型有一组初始化器将其置于一致状态,并且此状态是其他类所观察到的第一个状态。 static初始化器和类变量初始化器按文本顺序执行,并且可能不引用在类声明声明的类变量,即使这些类变量在作用域内(第8.3.3节)。 此限制旨在在编译时检测大多数循环或其他格式不正确的初始化。

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

enum类型在此处的Java语言规范中定义

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

您在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 。 因此访问是允许的。

我们可以深入到详细的类初始化过程中,这解释了每个类(类,接口,枚举)都是独立初始化的。 但是,我们可以创建一个可以看到尚未初始化值的示例

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);
        }
    }
}

这将打印null 。 要访问max被允许的enum类型的构造虽然我们的程序执行到达之前访问max被初始化。 JLS警告这一点

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


您的原始Foo示例引入了一个额外的规则,在Enum正文声明的章节中定义。

除非字段是一个常量变量(§4.12.4),否则从构造函数,实例初始值设定项或枚举类型的实例变量初始值设定项表达式引用枚举类型的静态字段时会出现编译时错误。

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

enum常量转换为public static final字段。 这些文本首先在enum类型定义中出现,因此首先进行初始化。 它们的初始化涉及构造函数。 规则的存在是为了防止构造函数看到其他类变量的未初始化值,这些变量将在稍后被初始化。


嵌套类的位置没有意义。 您可以在声明它之前(在源文本的早期)引用嵌套类,与在声明它们之前可以引用方法相同。

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

所以,让我们说既不Bar也不Baz被初始化。

如果某些代码需要Bar那么Bar将被初始化。 Baz将不会初始化,因为Bar没有提到Baz

如果有些代码需要Baz (它们都没有初始化),那么Baz初始化就开始了。 当ABaz构造函数开始运行时, Bar仍未初始化。 第一行然后需要Bar ,并且Bar初始化开始。 在执行first.add(description)语句之前, Bar初始化完全正常完成。 第二条语句也可以执行,因为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可以使用它。

你也可以看到, Baz静态初始化器不会运行,直到构造枚举之后,这当然是为什么你不能从构造函数中引用静态成员。

链接地址: http://www.djcxy.com/p/38077.html

上一篇: Private enums and static fields in the enclosing class

下一篇: Accessing enum arguments outside of constructor?