Java初始化和实例化的顺序
所以我试图把JVM中的初始化和实例化的过程拼凑起来,但是JLS在一些细节方面稍微有一点点缺陷,所以如果有人会介意清理一些细节的话,那么这个细节就可以被理解了。 这是我到目前为止所能弄清楚的。
初始化
-
recursion初始化类的静态最终variables和它的编译时间常量的接口。
-
退出按照文本顺序处理静态块和静态字段的recursion。
实例化
-
recursion初始化编译时间常量的类的最终实例variables。
-
退出处理非静态块和实例字段的recursion处理文本顺序,在返回时将它们添加到构造函数中。
好的,那么现在的问题。
-
接口按声明的顺序处理?
-
是在一个单独的recursion堆栈中处理的接口?
a)如果是的话,接口在超类之前还是之后被处理?
b)如果是的话,我是否正确推断其中一个或其他(接口或超类)获取其他编译时常量之前已初始化其非编译时常量字段。
-
调用非默认super()构造函数在这个过程中起什么作用?
-
我的结论是否有误?
-
我是否错过了其他关键细节?
区分类的初始化和对象的初始化很重要。
类初始化
一个类或接口在初次访问时被初始化,分配编译时间常量字段,然后recursion地初始化超类(如果尚未初始化的话),然后处理静态初始值设定项(包括非编译时间的静态字段的初始值设定项常量)。
正如你已经注意到的,一个类的初始化本身不会触发它实现的接口的初始化。 因此,接口首次被访问时被初始化,通常通过读取一个不是编译时间常量的字段 。 在初始化程序的评估过程中可能发生这种访问,导致recursion初始化。
还值得注意的是,初始化不是通过访问编译时间常量的字段触发的,因为这些是在编译时计算的 :
对常量variables(§4.12.4)的字段的引用必须在编译时parsing为由常量variables初始值表示的值V.
如果这样的字段是静态的,那么在二进制文件的代码中不应该包含字段的引用,包括声明该字段的类或接口。 这样的字段必须总是被初始化(§12.4.2); 永远不能观察字段的默认初始值(如果不同于V)。
如果这样的字段是非静态的,那么除了包含该字段的类外,二进制文件中的代码中不应该存在对该字段的引用。 (它将是一个类而不是一个接口,因为一个接口只有静态字段。)在创build实例时(§12.5),类应该有代码来将字段的值设置为V.
对象初始化
每当创build一个新对象时,一个对象就被初始化,通常是通过评估一个类实例创buildexpression式。 这进行如下:
将构造函数的参数分配给此构造函数调用的新创build的参数variables。
如果此构造函数以相同类中的另一个构造函数(使用此构造函数)的显式构造函数调用(第8.8.7.1节)开始,则使用这五个相同的步骤评估参数并recursion构造函数调用。 如果构造函数的调用突然完成,则此过程由于同样的原因突然完成; 否则,继续步骤5。
这个构造函数不是从同一个类中的另一个构造函数的显式构造函数调用开始的(使用这个)。 如果这个构造函数是针对Object以外的类的,那么这个构造函数将以一个超类构造函数的显式或隐式调用(使用super)开始。 评估参数并使用这五个相同的步骤recursion地处理超类构造函数的调用。 如果构造函数的调用突然完成,那么出于同样的原因,此过程突然完成。 否则,继续执行第4步。
执行此类的实例初始化程序和实例variables初始化程序,将实例variables初始化程序的值分配给相应的实例variables,按照从左到右的顺序,它们以文本forms显示在类的源代码中。 如果执行这些初始化程序中的任何一个都会导致exception,则不会执行进一步的初始化程序,并且此过程会以相同的exception突然完成。 否则,继续执行步骤5。
执行此构造函数的其余部分。 如果该执行突然完成,则此过程由于相同的原因而突然完成。 否则,此过程正常完成。
正如我们在步骤3中看到的那样,对超级构造函数的显式调用的存在仅仅改变了哪个超类构造函数被调用。