【问题标题】:Are static initialisers necessarily executed in hierarchical order?静态初始化程序是否必须按层次顺序执行?
【发布时间】:2018-01-23 14:35:47
【问题描述】:

假设我有一堂课

class A{
    public final static TreeMap<String,String> tmap = new TreeMap<>();
    int x;

    static{
        tmap.put("x:I", "Hello");
    }
}

我创建了一个子类

class B extends A{
    long y;

    static{
        tmap.put("y:J","World");
    }
}

如果我现在写一些代码来检查静态初始化器:

class Main{
    public static void main (String[] args){
        B b = new B();
        for(String v : b.tmap.values()){
            System.out.println(v);
        }
    }
}

我知道这两个条目必须在 tmap 中,因为 A 最终必须被加载,以便最迟 B 的超级调用。

但如果我正确读取了When does static class initialization happen?,我不能假设Hello 值总是首先放入映射中,因为tmap 是最终值。

因此,如果排序很重要(例如,如果我知道某些值可能会在层次结构中进一步更新/覆盖),我是否需要删除 final 修饰符?

或者是否有其他东西已经强制执行“正确的”静态初始化程序排序?

【问题讨论】:

  • 在您的示例中,添加实体的顺序并不重要,因为 TreeMap 会自动按照键的自然顺序对它们进行排序。作为此问题的示例,LinkedHashMap 可能会更好。
  • 为什么你认为final跟订单有关系?
  • @SotiriosDelimanolis 因为链接到问题的答案强调静态初始化器在第一次使用 非常量 静态字段之前被调用。静态最终字段常量。
  • 如果您点击该答案中的 JLS 链接,您会发现它表示一个常量变量finalstatic 类型为 TreeMap 的字段不是常量变量

标签: java static classloader operator-precedence static-initializer


【解决方案1】:

父类的静态初始化总是在子类初始化之前完成,如果你有多个静态初始化块,它们会按顺序执行。 (编辑: 感谢 Holger 指出加载类时不必直接这样做)。

在您的示例中,B 扩展了 A,因此类加载器必须在 B 之前加载 A。因此,首先执行 A 的静态初始化程序。

这与 final 修饰符无关。另一个线程可能指的是,如果编译器可以执行常量折叠(https://www.javaworld.com/article/2076060/build-ci-sdlc/compiler-optimizations.html),则获取常量的类将不会被静态初始化(因为在编译时会替换引用)。

【讨论】:

  • 您的意思是“类加载器必须在 B 之前加载 A”?否则,您的“A 的静态启动器首先执行”的结论并不十分明显。
  • 这是错误的。初始化不是“在加载类后立即完成”。 The specification 准确地说明了哪些事件可能会触发类初始化。加载的类的初始化程序完全有可能永远不会运行。但幸运的是,有明确的声明,“当一个类被初始化时,它的超类被初始化(如果它们之前没有被初始化)......”,所以A的初始化器确实在@之前执行987654327@的,但不是因为你的推理。
  • @Holger 感谢您指出这一点。在答案中修复它
猜你喜欢
  • 1970-01-01
  • 2021-07-12
  • 1970-01-01
  • 2019-09-17
  • 2010-09-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多