在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这个命令是:
- 静态初始化块
- 实例初始化块
- 构造函数
编辑
感谢您的意见,现在我可以引用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)构造函数都会被调用。
完整的解释
执行的顺序就像,
- 静态块
- 实例块
- 构造函数
说明
在任何情况下, 静态块总是只在一开始就被调用一次 ,在你的情况下,当你运行程序的时候。 (这就是静态块的意思)。 它不依赖于实例,因此在创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编译器将初始化块复制到每个构造函数中。 因此,这种方法可以用来在多个构造函数之间共享一段代码。