【问题标题】:Private enums and static fields in the enclosing class封闭类中的私有枚举和静态字段
【发布时间】:2016-08-17 16:30:14
【问题描述】:

我明白为什么枚举构造函数不能访问静态字段 和枚举本身内的方法,以及为什么允许这样做 在课堂上。以下面的代码为例,

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

【问题讨论】:

  • 有趣的问题。不过,我不确定您在静态字段初始化之前调用构造函数的假设是否正确。我认为情况恰恰相反。证明类型:stackoverflow.com/questions/36407743/…
  • @SotiriosDelimanolis 实际上枚举常量列表或多或少是使用枚举的构造函数创建的对象列表。
  • @SotiriosDelimanolis 示例已提供,谢谢。很抱歉。

标签: java enums static


【解决方案1】:

这在 JLS 中有点到处都是。 When Initialization Occurs chapter 状态

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

那个加粗的 sn-p 指的是直接包含访问的类。

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

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

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


您原来的 Foo 示例引入了一个额外的规则,在 chapter on Enum Body Declarations 中定义。

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

该规则阻止您的Foo sn-p 编译。

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

【讨论】:

    【解决方案2】:

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

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

    所以,假设BarBaz 都没有被初始化。

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

    但是,如果某些代码需要Baz(它们都还没有初始化),那么Baz 初始化就开始了。当ABaz 构造函数开始运行时,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 都可以完全使用它。

    您还可以看到,Baz 静态初始化程序直到 枚举被构造后才会运行,这当然是您不能从构造函数中引用静态成员的原因。 p>

    【讨论】:

    猜你喜欢
    • 2017-05-12
    • 2020-02-16
    • 1970-01-01
    • 1970-01-01
    • 2012-03-05
    • 2014-06-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多