Java基础问题澄清 - 构造函数和“静态”?

12
class test
{
    test() {
        System.out.println("Constructor");
    }

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


public class sample
{
    public static void main(String [] a) {
        test t = new test();        
    }
}

在上面的代码中,为什么在调用test()构造函数之前就打印出了“Hai”?
在test类中的test()构造函数在“Hai”语句之上,并且应该首先被调用,对吧?
4个回答

36

让我们用一个更清晰的例子来说明:

public class Test {

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

    {
         System.out.println("instance initializer");
    }

    public Test() {
         System.out.println("constructor");
    }

}

并按以下方式进行测试:

public class Main {

    public static void main(String[] args) {
        Test test1 = new Test();
        Test test2 = new Test();
    }

}

输出:

static initializer
instance initializer
constructor
instance initializer
constructor
静态初始化器只在运行时执行一次,具体在加载类时执行。实例初始化器在每个实例化之前的构造函数中执行。
您可以拥有多个实例初始化器,并且它们将按照它们在代码中出现的顺序执行。
实例初始化器的主要优点是无论使用哪个构造函数都会执行它们。它们适用于每个构造函数,因此您不需要在所有构造函数中重复常见的初始化。
静态初始化器的主要优点是只在类加载期间执行一次。一个众所周知的现实世界的例子是JDBC驱动程序。当你执行以下操作时:
 Class.forName("com.example.jdbc.Driver");

如果只执行静态初始化程序,那么任何(好的)JDBC驱动程序将会按照以下方式在DriverManager中注册自己。

 static {
      DriverManager.registerDriver(new com.example.jdbc.Driver());
 }

这样DriverManager可以在执行getConnection()时找到正确的JDBC驱动程序。


7

2
那么 static 关键字是可选的吗? - blackanchorage
如果在上述代码中创建一个静态块,那么静态块和静态初始化块哪个先运行?能否解释一下什么是静态初始化器? - Hariharbalaji
1
这绝对不是一个“静态”初始化程序。 - BalusC
哎呀,你说得对,它根本不是静态的,而是一个实例初始化器。已修复。 - Bombe
5
由于有许多人不理解,或者因为拥有10,000多个声望的用户“必须”是正确的,所以需要进行翻译。请注意,翻译过程中应尽可能保持原意,同时使其更易于理解。 - BalusC

3
在上面的代码中,为什么括号内的语句(即“Hai”)在构造函数执行之前被打印出来?
因为这是预期的行为,正如Java语言规范12.5创建新类实例部分所描述的那样 :)
在返回新创建的对象的引用作为结果之前,将处理指定的构造函数以使用以下过程初始化新对象: 1. 为此构造函数的新创建参数变量分配构造函数的参数。 2. 如果这个构造函数以同一类中的另一个构造函数的显式构造函数调用开始(使用“this”),则评估参数并递归地使用这些相同的五个步骤处理该构造函数调用。如果该构造函数调用突然完成,则出于相同的原因,该过程也会突然完成;否则,请继续进行第5步。 3. 这个构造函数不以同一类中的另一个构造函数的显式构造函数调用开始(使用“this”)。如果这个构造函数是其他类的构造函数,则此构造函数将以超类构造函数的显式或隐式调用(使用“super”)开始。评估参数,并递归地使用这些相同的五个步骤处理该超类构造函数调用。如果该构造函数调用突然完成,则出于相同的原因,该过程也会突然完成。否则,请继续进行第4步。 4. 执行该类的实例初始化程序和实例变量初始化程序,按文本源代码中它们出现的从左到右的顺序为相应的实例变量分配实例变量初始化程序的值。如果执行其中任何一个初始化程序导致异常,则不会处理进一步的初始化程序,并且该过程将使用相同的异常突然完成。否则,继续进行第5步。(在一些早期的实现中,如果字段初始化程序表达式是常量表达式并且其值等于其类型的默认初始化值,则编译器错误地省略了初始化字段的代码。) 5. 执行这个构造函数的其余部分。如果该执行突然完成,则出于相同的原因,该过程也会突然完成。否则,该过程将正常完成。
请参阅8.6 实例初始化程序部分,以获取更多有关实例初始化程序的详细信息。

2

类内部的大括号立即引入了一个实例初始化器(Java 1.1引入)。它们的处理方式与作为声明的一部分编写的分配字段的代码非常相似。因此,以下两种写法是等效的:

 private final Thing x = new Thing();

并且

 private final Thing x;
 {
     x = new Thing();
 }

代码在调用 super 构造函数后立即由构造函数执行。因此,假设没有其他初始化,代码可以等效地编写为构造函数的一部分:
 private final Thing x;
 public MyCLass() {
     super(); // Often implicit.
     x = new Thing();
 }

同样位置上的大括号,先于static关键字和静态初始化程序,在类初始化时执行一次,而不是每个实例基础上执行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接