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

  • 本文关键字:Java java braces
  • 更新时间 :
  • 英文 :


当我运行以下代码时:

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

有人能解释一下为什么它以相反的顺序输出吗?

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

此外,当我创建多个Test:实例时

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

静态块在第一时间仅执行

这一切都取决于初始化语句的执行顺序。您的测试表明此订单为:

  1. 静态初始化块
  2. 实例初始化块
  3. 施工人员

编辑

感谢您的评论,现在我可以引用JVM规范中适当的部分了。这里是详细的初始化过程。

3-是一个静态初始值设定项,它在加载类时运行一次,这首先发生。

2-是一个初始化器块,java编译器实际上会将其复制到每个构造函数中,因此如果您愿意,可以在构造函数之间共享一些初始化。很少使用。

1-将在构造对象时执行,在(3(和(2(之后。。

这里的更多信息

首先执行静态块。

然后实例实例初始化阻止

请参阅JLS,例如初始化程序

//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(我假设您是从命令行执行的(将找到并运行主方法。主静态方法初始化其构造函数被调用的实例,因此最后打印的是"1"。

因为static{}代码是在类首次在JVM中初始化时运行的(即,甚至在调用main()之前(,所以在实例首次初始化时,在构造实例之前,会调用实例{},然后在完成所有这些之后调用构造函数。

我在这里通过ASM获得了类似字节码的代码。

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

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首先出现的原因。由于这是在类级别,而不是对象级别,它将显示一次,因为类类型将加载一次。有关详细信息,请参阅Java2虚拟机内部:类型的寿命

LDC "2"`LDC "1"在构造函数"init"中。

按此顺序排列的原因是:构造函数将首先执行一些隐含指令,如类的{}中的超级构造函数和代码,然后执行其构造函数显式中的代码。

这就是编译器将对java文件执行的操作。

似乎没有人明确说明为什么3只打印一次。所以我想补充一点,这与为什么它首先被打印有关。

静态定义的代码被标记为与类的任何特定实例分离。通常,静态定义的代码可以被认为根本不是任何类(当然,当考虑范围时,该语句中存在一些无效性(。因此,一旦加载类,代码就会运行,如上所述,在中,当构造实例Test()时,它不会被调用,因此多次调用构造函数将不会导致静态代码继续运行。

如上所述,包含2的带括号的代码被预先添加到构造中,因为它在某种程度上是类中所有构造函数的先决条件。您不知道Test的构造函数中会发生什么,但可以保证它们都是从打印2开始的。因此,这发生在任何特定构造函数中的任何事情之前,并且在每次调用(ny(构造函数时都会被调用。

完整说明

执行顺序类似于

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

解释

静态块在一开始总是只调用一次,无论何时通过任何方式访问类,在您运行程序的情况下。(这就是静态块的作用(。它不依赖于实例,因此在创建新实例时不会再次调用。

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

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

编译后代码将变为,

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编译器将初始值设定项块复制到每个构造函数中。因此,这种方法可以用于在多个构造函数之间共享一个代码块。

相关内容

  • 没有找到相关文章

最新更新