【问题标题】:In what order do static/instance initializer blocks in Java run?Java 中的静态/实例初始化程序块以什么顺序运行?
【发布时间】:2011-01-01 17:38:24
【问题描述】:

假设一个项目包含多个类,每个类都有一个静态初始化块。这些块以什么顺序运行?我知道在一个类中,这些块按照它们在代码中出现的顺序运行。我读过它在各个类中都是一样的,但是我写的一些示例代码不同意这一点。我使用了这段代码:

package pkg;

public class LoadTest {
    public static void main(String[] args) {
        System.out.println("START");
        new Child();
        System.out.println("END");
    }
}

class Parent extends Grandparent {
    // Instance init block
    {
        System.out.println("instance - parent");
    }

    // Constructor
    public Parent() {
        System.out.println("constructor - parent");
    }

    // Static init block
    static {
        System.out.println("static - parent");
    }
}

class Grandparent {
    // Static init block
    static {
        System.out.println("static - grandparent");
    }

    // Instance init block
    {
        System.out.println("instance - grandparent");
    }

    // Constructor
    public Grandparent() {
        System.out.println("constructor - grandparent");
    }
}

class Child extends Parent {
    // Constructor
    public Child() {
        System.out.println("constructor - child");
    }

    // Static init block
    static {
        System.out.println("static - child");
    }

    // Instance init block
    {
        System.out.println("instance - child");
    }
}

得到这个输出:

开始
静态 - 祖父母
静态 - 父
静态 - 孩子
实例 - 祖父母
构造函数 - 祖父母
实例 - 父
构造函数 - 父
实例 - 子
构造函数 - 子
结束

显而易见的答案是父母的积木在他们孩子的之前运行,但这可能只是巧合,如果两个类不在同一个层次结构中,则无济于事。

编辑:

我通过将它附加到 LoadTest.java 来修改我的示例代码:

class IAmAClassThatIsNeverUsed {
    // Constructor
    public IAmAClassThatIsNeverUsed() {
        System.out.println("constructor - IAACTINU");
    }

    // Instance init block
    {
        System.out.println("instance - IAACTINU");
    }

    // Static init block
    static {
        System.out.println("static - IAACTINU");
    }
}

正如类名所暗示的那样,我从未在任何地方引用过新类。新程序产生的输出与旧程序相同。

【问题讨论】:

标签: java static-initializer


【解决方案1】:

请参阅JLS version 8 的第 12.4 和 12.5 节,他们详细介绍了所有这些(静态变量为 12.4,实例变量为 12.5)。

对于静态初始化(第 12.4 节):

类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:

  • T 是一个类,并创建了一个 T 的实例。
  • T 是一个类,调用了 T 声明的静态方法。
  • 分配了一个由 T 声明的静态字段。
  • 使用了由 T 声明的静态字段,并且该字段不是常量变量(第 4.12.4 节)。
  • T 是一个顶级类(第 7.6 节),并且会执行在词法上嵌套在 T(第 8.1.3 节)中的断言语句(第 14.10 节)。

(和几个鼬鼠词从句)

【讨论】:

  • 最佳答案。干得好!
  • 好答案。我浏览了该页面,其中包含与 Java 相关的面向对象设计的基础知识。所以它是每个java开发者的必读之物。
  • 好一个。直接来自创作者
  • 这个答案涵盖了所有内容。它应该是公认的答案。
  • 我相信 JLS 的这一部分是不精确的(如果你想稍微不准确的话)。第 4 项应该是:“使用了由 T 声明的静态字段,并且该字段不是具有字面值初始值设定项的常量变量。”作为示例,请参阅:pastebin.com/1XfczpjRC.main 打印 42,但根据上述规则,B 不应该被初始化,因为answer 是一个常量变量(与 JSL 4.12.4 相比)。
【解决方案2】:

类的静态初始化程序在第一次访问该类时运行,以创建实例或访问静态方法或字段。

因此,对于多个类,这完全取决于为加载这些类而运行的代码。

【讨论】:

  • 一个类可以在依赖的解析周期中被加载。只需声明给定类的变量,就需要加载该类。
  • 这似乎与第 12.4 节(参见 Keith 的出色回答)所说的相反。
  • 我同意。变量声明(我认为)不会这样做,除非它是带有初始化的静态变量。
  • 你读过Java Language Specification吗?加载类时,静态初始化不会运行。
  • 除非该类被另一个类引用时加载,这可能需要很长时间才能初始化。
【解决方案3】:

Keith 和 Chris 的回答都很棒,我只是为我的具体问题添加更多细节。

静态初始化块按照其类的初始化顺序运行。那么,那是什么顺序呢?根据 JLS 12.4.1:

类或接口类型 T 将在以下任何一项第一次出现之前立即初始化:

  • T 是一个类,并创建了一个 T 的实例。
  • T 是一个类,调用了 T 声明的静态方法。
  • 分配了一个由 T 声明的静态字段。
  • 使用了由 T 声明的静态字段,并且该字段不是常量变量(第 4.12.4 节)。
  • T 是一个顶级类,并且执行一个在词法上嵌套在 T 中的断言语句(第 14.10 节)。

在类 Class 和包 java.lang.reflect 中调用某些反射方法也会导致类或接口初始化。在任何其他情况下,类或接口都不会被初始化。

为了说明,下面是示例中发生的情况的演练:

  1. 进入主界面
  2. 打印“开始”
  3. 尝试创建 Child 的第一个实例,这需要初始化 Child
  4. 尝试初始化 Child 会导致 Parent 初始化
  5. 尝试初始化 Parent 会导致 Grandparent 初始化
  6. 在祖父母初始化开始时,运行祖父母的静态初始化块
  7. 从技术上讲,Object 由于是祖父母的父母而在初始化链中拥有最后的发言权,但它没有任何贡献
  8. Grandparent的静态初始化块结束后,程序回退到Parent的静态初始化块
  9. Parent的静态初始化块结束后,程序回退到Child的静态初始化块
  10. 此时 Child 已初始化,其构造函数可以继续进行
  11. 由于 IAmAClassThatIsNeverUsed 从未被引用,因此它的任何代码都不会运行,包括静态初始化程序块
  12. 本演练的其余部分不涉及静态初始化程序,只是为了完整性而包含在内
  13. Child 的构造函数隐式调用 super()(即 Parent 的构造函数)
  14. Parent 的构造函数隐式调用 super()(即,Grandparent 的构造函数)
  15. Grandparent 的构造函数也是如此,但没有任何作用(再次说明,Object 没有任何贡献)
  16. Grandparent 的构造函数调用 super() 后,紧接着是 Grandparent 的实例初始化块
  17. Grandparent 的构造函数的其余部分运行,构造函数终止
  18. 程序在调用 super()(即祖父母的构造函数)解析后立即回退到父的构造函数
  19. 如上所述,Parent 的实例初始化器完成了它的工作,它的构造器完成了
  20. 同理,程序返回并完成Child的构造函数
  21. 此时,对象已被实例化
  22. 打印“END”
  23. 正常终止

【讨论】:

  • 在 12.4.1 中我们还发现:Before a class is initialized, its direct superclass must be initialized, ...
【解决方案4】:

类的初始化包括执行其静态初始化程序和类中声明的静态字段(类变量)的初始化程序。

接口的初始化包括执行接口中声明的字段(常量)的初始化程序。

在一个类被初始化之前,它的直接超类必须被初始化,但是类实现的接口没有被初始化。同样,接口的超接口在接口初始化之前也不会被初始化。

【讨论】:

    【解决方案5】:

    有一种情况不会调用静态块。

    class Super {
        public static int i=10;
    }
    class Sub extends Super {
        static {
            system.out.println("Static block called");
        }
    }
    class Test {
        public static void main (String [] args) {
            system.out.println(Sub.i);
        } 
    }
    

    以上代码输出10


    从“编辑器”更新

    对此的技术解释在JLS 12.4.1

    “对静态字段的引用(第 8.3.1.1 节)只会初始化实际声明它的类或接口,即使它可能通过子类、子接口或一个实现接口的类。”

    直观的解释是Super.iSub.i实际上是同一个变量,Sub中的任何东西实际上都不需要初始化Super.i才能得到正确的值。

    (如果Super.i的初始化表达式引用了Sub类,那就不一样了。但是你会在初始化顺序上有一个循环。仔细阅读JLS 12.4.1JLS 12.4.2解释说这是允许的,并允许您准确计算出实践中会发生什么。)

    【讨论】:

      【解决方案6】:

      你可以在同一个类中有多个静态和实例初始化器,因此

      • 静态初始化程序按照它们声明的文本顺序调用(来自12.4.2
      • 实例初始化程序按照它们声明的文本顺序调用(来自12.5

      每个都像单个块一样执行。

      【讨论】:

        【解决方案7】:
        class A {
          public A() { 
            // 2
          }
        }
        
        class B extends A{
          static char x = 'x'; // 0
          char y = 'y'; // 3
          public B() { 
            // 4
          }
        
          public static void main(String[] args) {
            new B(); // 1
          }
        }
        
        

        注释中的数字表示评估顺序,越小越早。

        如示例所示,

        1. 静态变量
        2. 主要
        3. 超类的构造函数
        4. 实例变量
        5. 构造函数

        【讨论】:

          【解决方案8】:

          http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html

          请检查 java 文档。

          然后明确提到无论静态块有多少,它们都将按照它们出现的顺序作为单个块执行

          所以,

          我的理解是java正在查看您的代码

          static{
          i=1;
          i=2;
          }
          

          静态整数 i;

          这就是你得到输出 2 的原因

          希望对你有帮助

          【讨论】:

          • 这只是重复问题表明 OP 已经知道的信息。
          猜你喜欢
          • 1970-01-01
          • 2010-10-22
          • 1970-01-01
          • 1970-01-01
          • 2019-05-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-02
          相关资源
          最近更新 更多