Java 中的静态/实例初始化程序块按什么顺序运行?

发布于 2024-08-16 20:42:00 字数 1997 浏览 8 评论 0原文

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

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

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

Say a project contains several classes, each of which has a static initializer block. In what order do those blocks run? I know that within a class, such blocks are run in the order they appear in the code. I've read that it's the same across classes, but some sample code I wrote disagrees with that. I used this code:

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

and got this output:

START
static - grandparent
static - parent
static - child
instance - grandparent
constructor - grandparent
instance - parent
constructor - parent
instance - child
constructor - child
END

The obvious answer from that is that parents' blocks run before their children's, but that could just be a coincidence and doesn't help if two classes aren't in the same hierarchy.

EDIT:

I modified my example code by appending this to 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");
    }
}

As implied by the class name, I never referenced the new class anywhere. The new program produced the same output as the old one.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

娇妻 2024-08-23 20:42:00

请参阅 JLS 版本的第 12.4 和 12.5 节8,他们详细介绍了所有这些(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 节)。

(以及一些狡猾的词句)

See section 12.4 and 12.5 of the JLS version 8, they go into gory detail about all of this (12.4 for static and 12.5 for instance variables).

For static initialization (section 12.4):

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top level class (§7.6), and an assert statement (§14.10) lexically nested within T (§8.1.3) is executed.

(and several weasel-word clauses)

酒绊 2024-08-23 20:42:00

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

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

The static initializer for a class gets run when the class is first accessed, either to create an instance, or to access a static method or field.

So, for multiple classes, this totally depends on the code that's run to cause those classes to get loaded.

终陌 2024-08-23 20:42:00

基思和克里斯的答案都很好,我只是为我的具体问题添加一些更多细节。

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

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

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

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

为了说明这一点,以下是示例中发生的情况的演练:

  1. Enter main
  2. Print "START"
  3. 尝试创建 Child 的第一个实例,这需要初始化 Child
  4. 尝试初始化 Child 会导致 Parent 的初始化
  5. 尝试初始化 Parent 会导致 Grandparent 的初始化
  6. 在 Grandparent 初始化开始时,运行 Grandparent 的静态初始化块
  7. 从技术上讲,对象拥有最后发言权在初始化链中,由于是 Grandparent 的 Parent,但它没有任何贡献
  8. 在 Grandparent 的静态初始化块结束后,程序回退到 Parent 的静态初始化块
  9. 在 Parent 的静态初始化之后块结束,程序回退到 Child 的静态初始化块
  10. 此时,Child 已初始化,因此其构造函数可以继续
  11. 由于 IAmAClassThatIsNeverUsed 从未被引用,因此它的任何代码都不会运行,包括静态初始值设定项块
  12. 本演练的其余部分不涉及静态初始值设定项,只是为了完整性而包含
  13. 在内
  14. 。构造函数)
  15. 祖父母的构造函数执行相同的操作,但没有任何效果(同样,对象没有任何贡献)
  16. 祖父母的构造函数调用 super() 后立即出现祖父母的实例初始化程序块
  17. 祖父母的构造函数的其余部分运行并且构造函数终止
  18. 程序回退在对 super()(即祖父的构造函数)的调用解析之后,立即返回到父级的
  19. 构造函数。如上所述,父级的实例初始值设定项执行其操作,并且其构造函数完成。
  20. 类似地,程序返回并完成子级的构造函数
  21. 此时,该对象已完成已实例化
  22. 打印“END”
  23. 正常终止

Keith's and Chris's answers are both great, I'm just adding some more detail for my specific question.

Static init blocks run in the order in which their classes are initialized. So, what order is that? Per JLS 12.4.1:

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization. A class or interface will not be initialized under any other circumstance.

To illustrate, here's a walkthrough of what's happening in the example:

  1. Enter main
  2. Print "START"
  3. Attempt to create first instance of Child, which requires initialization of Child
  4. Attempting to initialize Child causes initialization of Parent
  5. Attempting to initialize Parent causes initialization of Grandparent
  6. At the start of initialization of Grandparent, Grandparent's static initialization block is run
  7. Technically, Object gets the last say in the initialization chain by virtue of being Grandparent's parent, but it has nothing to contribute
  8. After Grandparent's static initialization block ends, program falls back to Parent's static initialization block
  9. After Parent's static initialization block ends, program falls back to Child's static initialization block
  10. At this point, Child is initialized, so its constructor may proceed
  11. Since IAmAClassThatIsNeverUsed never gets referenced, none of its code ever runs, including static initializer blocks
  12. The rest of this walkthrough doesn't concern static initializers and is included only for completeness
  13. Child's constructor implicitly calls super() (i.e., Parent's constructor)
  14. Parent's constructor implicitly calls super() (i.e., Grandparent's constructor)
  15. Grandparent's constructor does the same, which has no effect (again, Object has nothing to contribute)
  16. Immediately after Grandparent's constructor's call to super() comes Grandparent's instance initializer block
  17. The rest of Grandparent's constructor's constructor runs and the constructor terminates
  18. The program falls back to Parent's constructor, immediately after its call to super() (i.e., Grandparent's constructor) resolves
  19. As above, Parent's instance initializer does its thing and its constructor finishes up
  20. Similarly, the program returns to and completes Child's constructor
  21. At this point, the object has been instantiated
  22. Print "END"
  23. Terminate normally
三生殊途 2024-08-23 20:42:00

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

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.1 JLS 12.4.2 解释说这是允许的,并允许您准确地计算出实践中会发生什么。)

There is one case in which a static block will not be called.

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

The above code outputs 10


Update from an "editor"

The technical explanation for this is in JLS 12.4.1

"A reference to a static field (§8.3.1.1) causes initialization of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface."

The intuitive explanation is Super.i and Sub.i are actually the same variable, and nothing in Sub actually needs to be initialized for the Super.i to get the correct value.

(It would be different if the initialization expression for Super.i referred to the Sub class. But then you would have a cycle in the initialization order. A careful reading of JLS 12.4.1 and JLS 12.4.2 explains that this is allowed, and allows you to work out exactly what would happen in practice.)

深海少女心 2024-08-23 20:42:00

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

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

在初始化类之前,必须初始化其直接超类,但不初始化该类实现的接口。同样,接口初始化之前不会初始化接口的超级接口。

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class.

Initialization of an interface consists of executing the initializers for fields (constants) declared in the interface.

Before a class is initialized, its direct superclass must be initialized, but interfaces implemented by the class are not initialized. Similarly, the superinterfaces of an interface are not initialized before the interface is initialized.

私藏温柔 2024-08-23 20:42:00

您可以在同一个类中拥有多个静态和实例初始化程序,因此

  • 静态初始化程序按照声明的文本顺序调用(来自 12.4.2
  • 实例初始值设定项按其声明的文本顺序调用(来自 12.5)

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

You can have multiple static and instance initializers in the same class, therefore

  • Static initializers are called in the textual order they are declared (from 12.4.2)
  • Instance initializers are called in the textual order they are declared (from 12.5)

Each is executed as if it was a single block.

旧故 2024-08-23 20:42:00
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. 构造函数
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
  }
}

Numbers in the comment indicate the evaluation order, the smaller, the earlier.

As the example showed,

  1. static variable
  2. main
  3. constructor of superclass
  4. instance variable
  5. constructor
念﹏祤嫣 2024-08-23 20:42:00

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

请检查java文档。

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

所以,

我的理解是java正在将您的代码视为

static{
i=1;
i=2;
}

static int i;

这就是为什么你得到输出 2

希望这有帮助

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

kindly check java documentation.

then clearly mentioned no matter how may static blocks are there they will be executed as a single block in the order they appear

So,

My understanding here is java is looking your code as

static{
i=1;
i=2;
}

static int i;

that is why you are getting output 2

hope this is helpful

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文