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

时间:2011-11-18 16:41:22

标签: 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();

静态块首次执行

9 个答案:

答案 0 :(得分:62)

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

  1. 静态初始化块
  2. 实例初始化块
  3. 构造
  4. 修改

    感谢您的评论,现在我可以引用JVM规范中的相应部分。 Here它是详细的初始化过程。

答案 1 :(得分:31)

3 - 是一个静态初始化程序,它在加载类时运行一次,这首先发生。

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

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

More information here

答案 2 :(得分:18)

首先执行静态块。

然后实例实例初始化

请参阅JLS了解instance intializers

{

// sop statement

}

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

答案 3 :(得分:5)

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

    {System.out.println("2");}

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

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

答案 4 :(得分:5)

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

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

答案 5 :(得分:4)

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

答案 6 :(得分:4)

我已经通过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”中,这是一个类初始值设定项。

对象的生命周期通常是:loading class - &gt;链接类 - &gt;类初始化 - &gt;对象实例化 - &gt;使用 - &gt; GC 。这就是为什么3首先出现的原因。因为这是在类级别而不是对象级别,所以它将出现一次,因为类类型将被加载一次。有关详细信息,请参阅inside the Java2 Virtual Machine : life time of a type

LDC "2"`LDC "1"位于构造函数“init”中。

它按此顺序排序的原因是:构造函数将首先在类的{}中执行一些implict指令,如超级构造函数和代码,然后执行其construtor中明确的代码。

这就是编译器对java文件的作用。

答案 7 :(得分:4)

完整解释

执行顺序如,

  
      
  1. 静态阻止
  2.   
  3. 实例块
  4.   
  5. 构造
  6.   

<强>解释

无论何时以任何方式访问类,

静态块始终只会被一次调用,在您运行程序的情况下。 (这就是静态块的意思)。它不依赖于实例,因此在创建新实例时不会再次调用它。

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

实例初始化块实际上在构造函数之前调用了吗?

编译后代码将成为

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

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

From this documentation

  

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

答案 8 :(得分:4)

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

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

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