深入分析Java對象的建構順序


對於下面的代碼,許多有經驗的程序員都沒能給出正確的答案。如果你能只看代碼給出的答案和文章末尾出給出大答案一致,那么你已經掌握了Java對象的建構順序,中間的分析可以不用看了。

 1 /**
 2  * 父類Foo,實現控制台輸出
 3  *
 4  * @author youngto
 5  * @since 2013-01-25
 6  */
 7 class Foo {
 8 
 9     private int index = 100;
10     
11     //靜態代碼塊
12     static {        
13         System.out.println("Foo static");
14     }
15     
16     //初始化代碼塊
17     {        
18         System.out.println("Foo initialization");
19     }
20     
21     public Foo() {
22         System.out.println("Foo constructor");
23         System.out.println(printIndex());
24     }
25     
26     protected int printIndex() {
27         return index;
28     }
29     
30 }
31 
32 /**
33  * 子類Bar,實現控制台輸出
34  *
35  * @author youngto
36  * @since 2013-01-25
37  */
38 public class Bar extends Foo{
39     
40     private int index = 100;
41     static Bar bar = new Bar();
42     
43     //靜態代碼塊
44     static{
45         System.out.println("Bar static");
46     }
47     
48     //初始化代碼塊
49     {
50         System.out.println("Bar initialization");
51     }
52     
53     public Bar() {
54        System.out.println("Bar constructor");
55        System.out.println(printIndex());
56     }
57     
58     @Override
59     protected int printIndex() {
60         System.out.println(bar);
61         return index;
62     }
63     
64     public static void main(String[] args) {
65         Foo foo = new Bar();
66         System.out.println(foo.printIndex());
67         foo = new Bar();
68     }
69 
70 }

 

 在對象建構過程中。為確保其正確性,以下事件一定會以固定順序發生:

a、從heap之中分配內存,用以存放全部的 instance 變量以及這個對象連同其 superclasses的實現專屬數據(implementation-specific data)。所謂「實現專屬數據」包括指向“class and method data的指針。

b、 對象的Instance變量被初始化為其相應的缺省值。

c、調用most derived class(最深層派生類)的構造函數(constructor)(注:事實上,構造函數被.class文件中的一個initialization method(初始化函數)替換了。Initialization method是名為<init>的特殊函數,由Java編譯器安放在.class文件里。其中包含[構造函數代碼]、[instance變量之初始化代碼],以及[調用superclass Initialization method]之代碼。)。構造函致做的第一件事就是調用superclass的構造函數。這個程序一直反復持續到 java.lang.object構適函數被調用為止。一定要記住,java.lang.object是一切java對象的base class。

d、所有對象的靜態代碼塊或靜態字段先獲得執行,優先級從父類開始。

e、在構造函數本體執行之前,所有 instance 變量的初值設定式(initializers)和初始化區(initialization blocks)先獲得執行,然后才執行構造函數本體。於是base class的構造函數最先執行,most derived class的構造函數最后執行。這使得任何class的構造函數都能放心大膽地使用其任何superclass 的instance 變量。

代碼分析:

1、根據a:從heap分配內存,用來存放Bar的instance變量(index和bar)、Foo的instance變量(index),和一份「實現專屬數據」。

2、根據b:instance變量被初始化為其相應缺省值,Bar的index被賦值為0,bar被賦值為null,Foo的index被賦值為0。

3、根據c:在代碼65行准備創建Bar的一個對象,調用Bar的構造函數立即調用其superclass Foo的構造函數,Foo構造函數立即調用其superclass java.lang.Object的構造函數。

4、根據d:java.lang.Object構造函數返回后,在Foo對象中執行靜態代碼塊輸出Foo static,Foo靜態代碼塊執行完,執行Bar靜態代碼此時代碼執行到41行,准備創建Bar的第二個對象,針對這個對象,又從步驟1開始重復全部過程。

5、將要創建第二個Bar對象代碼再次執行到Foo對象,由於靜態對象只初始化一次,所以不會再次執行Foo的靜態代碼塊,直接先執行初始化代碼塊輸出Foo initialization,再執行Foo構造函數本體,根據e:Foo的index被賦值為100(這是來迷惑你的),輸出Foo constructor;在Foo的構造函數中調用了printIndex函數,由於printIndex函數已經被子類Bar重寫所以此時調用的是Bar中的printIndex函數,根據e:此時代碼還未執行到Bar的構造函數本體,所以此時printIndex函數輸出缺省值bar=null,返回Bar的index缺省值0,因此這一步輸出null,0,Foo構造函數完成,返回。

6、繼續構建第二個Bar對象,執行Bar的初始化代碼塊,輸出Bar initialization,再執行構造函數,輸出Bar constructor。此時Bar的index賦值100,Bar的bar對象還正在構建中所以為null,輸出null,100,Bar構造函數完成。

7、Object reference bar指向heap之中最后創建完成的Bar對象,此時在第一個Bar的對象創建過程中而創建的第二Bar對象創建完成。

8、繼續第一個Bar對象的創建、執行Bar的靜態代碼塊,輸出Bar static。

9、從4-8步靜態代碼執行完成,繼續執行Foo的初始化代碼塊輸出Foo initialization,再執行Foo的構造函數本體輸出Foo constructor。在Foo的構造函數中調用了printIndex函數,由於printIndex函數已經被子類Bar重寫所以此時調用的是Bar中的printIndex函數,由於在構建第二個Bar對象時已經為靜態字段bar賦值,所以靜態變量bar此時有值,但實例變量index還是為缺省值0,輸出Bar@4633c1aa和0,Foo構造函數完成,返回。

10、執行Bar的初始化代碼塊輸出:Bar initialization,再執行Bar的構造函數輸出Bar constructor,Bar@4633c1aa和100,Bar構造函數完成。

11、Object reference bar指向heap之中最后創建完成的Bar對象,此時第一個Bar的對象創建完成。

12、66行調用bar對象函數輸出Bar@4633c1aa和100.

13、67行此時給bar引用從新賦值,又從步驟一重復全部過程,由於靜態只實例化一次所以輸出為:Foo initialization、Foo constructor、Bar@4633c1aa、0、Bar initialization、Bar constructor、Bar@4633c1aa、100

輸出結果為:

//代碼在65行建構第一個Bar對象
Foo static
//代碼執行到41行建構第二個Bar對象
Foo initialization
Foo constructor
null
0
Bar initialization
Bar constructor
null
100
//代碼執行到56行第二個Bar對象建構完,繼續建構第一個Bar對象
Bar static
Foo initialization
Foo constructor
Bar@4633c1aa
0
Bar initialization
Bar constructor
Bar@4633c1aa
100
//66行調用第一個Bar對象的函數輸出結果
Bar@4633c1aa
100
//67行此時第一個Bar對象執行完,給第一個Bar引用從新建構對象
Foo initialization
Foo constructor
Bar@4633c1aa
0
Bar initialization
Bar constructor
Bar@4633c1aa
100

Java Developers 67844123


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM