在Java中使用花括号的奇怪的行为

当我运行下面的代码:

public class Test { Test(){ System.out.println("1"); } { System.out.println("2"); } static { System.out.println("3"); } public static void main(String args[]) { new Test(); } } 

我希望得到这个顺序的输出:

 1 2 3 

但我得到的是相反的顺序:

 3 2 1 

任何人都可以解释为什么它是相反的顺序输出?

================

另外,当我创build多个Test实例时:

 new Test(); new Test(); new Test(); new Test(); 

静态块在第一次执行。

这一切都取决于初始化语句的执行顺序。 你的testingcertificate这个命令是:

  1. 静态初始化块
  2. 实例初始化块
  3. 构造函数

编辑

感谢您的意见,现在我可以引用JVM规范中的相应部分。 这里是详细的初始化程序。

3 – 是一个静态的初始化器,它在类加载时运行一次,首先发生。

2 – 是一个初始化块,java编译器实际上将它复制到每个构造函数中,所以如果你喜欢,你可以在构造函数之间共享一些初始化。 几乎没有使用过。

(3)和(2)之后,当你构造对象时,将被执行。

更多信息在这里

静态块首先被执行。

然后实例实例初始化块

请参阅JLS的例子intializers

{

// sop声明

}

就像构造函数一样,在实例初始化块中不能有返回语句。

 Test(){System.out.println("1");} {System.out.println("2");} static{System.out.println("3");} 

静态的东西首先被执行, {System.out.println("2");}不是一个函数的一部分,因为它的作用域是第一个,而Test(){System.out.println("1");}最后被调用,因为其他两个被首先调用

首先,将类加载到JVM中并进行类初始化。 在此步骤中,执行静态块。 “{…}”只是“static {…}”的语法等价物。 由于代码中已经有一个“static {…}”块,因此将会附加“{…}”。 这就是为什么你有2之前打印3。

接下来一旦加载类,java.exe(我假设你从命令行执行)将find并运行主要的方法。 主要的静态方法初始化其构造函数被调用的实例,所以你最后打印“1”。

由于static{}代码是在JVM中第一次初始化类时运行的(即,甚至在调用main()之前),当实例首次初始化之前调用实例{} ,然后构造函数所有这一切都完成之后再叫。

我已经通过ASM获得了类似于字节码的代码。

我认为这可以回答你的问题,解释在这种情况下创build对象时发生了什么。

 public class Test { static <clinit>() : void GETSTATIC System.out : PrintStream LDC "3" INVOKEVIRTUAL PrintStream.println(String) : void RETURN <init>() : void ALOAD 0: this INVOKESPECIAL Object.<init>() : void GETSTATIC System.out : PrintStream LDC "2" INVOKEVIRTUAL PrintStream.println(String) : void GETSTATIC System.out : PrintStream LDC "1" INVOKEVIRTUAL PrintStream.println(String) : void RETURN public static main(String[]) : void NEW Test INVOKESPECIAL Test.<init>() : void RETURN } 

我们可以看到LDC "3"在“clinit”中,这是一个类初始化器。

对象的生命周期通常是:加载类 – >链接类 – >类初始化 – >对象实例化 – >使用 – > GC。 这就是为什么3首先出现。 因为这是在类级别,而不是对象级别,它会出现一次类types将被加载一次。 有关详细信息,请参阅Java2虚拟机内部:types的生存时间

LDC "2"`LDC "1"是在“init”中的构造函数。

按照这个顺序的原因是:构造函数将首先在一个类的{}中执行超级构造函数和代码等隐含指令,然后执行代码。

这就是编译器会对java文件做的事情。

看起来好像没有人说明为什么3只是明确地打印一次。 所以我想补充一点,这与它为什么先打印有关。

静态定义的代码被标记为与该类的任何特定实例分离。 一般来说,静态定义的代码可以被认为不是任何类(当然,在考虑范围的时候,在这个语句中也有一些无效)。 因此,如上所述,代码在加载类之后就会运行,因为在构造实例Test()不会调用它,因此多次调用构造函数将不会导致静态代码再次运行。

如上所述,包含2的括号内的代码被前置到结构中,因为它是类中所有构造函数的先决条件。 你不知道在Test的构造函数中会发生什么,但是你可以保证它们都以打印2开头。 因此,这发生在任何特定构造函数之前,每调用一次(ny)构造函数都会被调用。

完整的解释

执行的顺序就像,

  1. 静态块
  2. 实例块
  3. 构造函数

说明

在任何情况下, 静态块总是只在一开始就被调用一次 ,在你的情况下,当你运行程序的时候。 (这就是静态块的意思)。 它不依赖于实例,因此在创build新实例时不再调用。

然后,将为每个创build的实例调用实例初始化块,然后为每个实例创build构造函数。 因为它们都可以用来实例化实例。

实例初始化块是否在构造函数之前实际调用?

编译之后代码就会变成,

 public class Test { Test(){ super(); System.out.println("2"); System.out.println("1"); } static { System.out.println("3"); } public static void main(String args[]) { new Test(); } } 

所以你可以看到,写在实例块本身的语句成为构造函数的一部分。 因此它在已经写在构造函数中的语句之前执行。

从这个文档

Java编译器将初始化块复制到每个构造函数中。 因此,这种方法可以用来在多个构造函数之间共享一段代码。