古詩詞大全網 - 個性簽名 - java程序的初始化

java程序的初始化

關於Java初始化,有多文章都用了很大篇幅的介紹。經典的<>更是用了專門的

壹章來介紹Java初始化。但在大量有代碼實例後面,感覺上仍然沒有真正深入到初始化的本質。

本文以作者對JVM的理解和自己的經驗,對Java的初始化做壹個比深入的說明,由於作者有水平限制,

以及JDK各實現版本的變化,可能仍然有不少錯誤和缺點。歡迎行家高手賜教。

要深入了解Java初始化,我們無法知道從程序流程上知道JVM是按什麽順序來執行的。了解JVM的執行

機制和堆棧跟蹤是有效的手段。可惜的是,到目前為止。JDK1。4和JDK1。5在javap功能上卻仍然存在

著BUG。所以有些過程我無法用實際的結果向妳證明兩種相反的情況(但我可以證明那確實是壹個BUG)

<>(第三版)在第四章壹開始的時候,這樣來描述Java的初始化工作:

以下譯文原文:

可以這樣認為,每個類都有壹個名為Initialize()的方法,這個名字就暗示了它得在使用之前調用,不幸

的是,這麽做的話,用戶就得記住要調用這個方法,java類庫的設計者們可以通過壹種被稱為構造函數的

特殊方法,來保證每個對象都能得到被始化.如果類有構造函數,那麽java就會在對象剛剛創建,用戶還來

不及得到的時候,自動調用那個構造函數,這樣初始化就有保障了。

我不知道原作者的描述和譯者的理解之間有多大的差異,結合全章,我沒有發現兩個最關鍵的字""

和""。至少說明原作者和譯者並沒有真正說明JVM在初始化時做了什麽,或者說並不了解JVM的初始化

內幕,要不然明明有這兩個方法,卻為什麽要認為有壹個事實上並不存在的"Initialize()"方法呢?

""和""方法在哪裏?

這兩個方法是實際存在而妳又找不到的方法,也許正是這樣才使得壹些大師都犯暈。加上jdk實現上的壹

些BUG,如果沒有深入了解,真的讓人摸不著北。

現在科學體系有壹個奇怪的現象,那麽龐大的體系最初都是建立在壹個假設的基礎是,假設1是正確的,

由此推導出2,再繼續推導出10000000000。可惜的是太多的人根本不在乎2-100000000000這樣的體系都

是建立在假設1是正確的基礎上的。我並不會用“可以這樣認為”這樣的假設,我要確實證明""

和""方法是真真實實的存在的:

packagedebug;

publicclassMyTest{

staticinti=100/0;

publicstaticvoidmain(String[]args){

Ssytem.out.println("Hello,World!");

}

}

執行壹下看看,這是jdk1.5的輸出:

java.lang.ExceptionInInitializerError

Causedby:java.lang.ArithmeticException:/byzero

atdebug.MyTest.(Test.java:3)

Exceptioninthread"main"

請註意,和其它方法調用時產生的異常壹樣,異常被定位於debug.MyTest的.

再來看:

packagedebug;

publicclassTest{

Test(){

inti=100/0;

}

publicstaticvoidmain(String[]args){

newTest();

}

}

jdk1.5輸入:

Exceptioninthread"main"java.lang.ArithmeticException:/byzero

atdebug.Test.(Test.java:4)

atdebug.Test.main(Test.java:7)

JVM並沒有把異常定位在Test()構造方法中,而是在debug.Test.。

當我們看到了這兩個方法以後,我們再來詳細討論這兩個“內置初始化方法”(我並不喜歡生造壹些

非標準的術語,但我確實不知道如何規範地稱呼他們)。

內置初始化方法是JVM在內部專門用於初始化的特有方法,而不是提供給程序員調用的方法,事實上

“<>”這樣的語法在源程序中妳連編譯都無法通過。這就說明,初始化是由JVM控制而不是讓程序員

來控制的。

類初始化方法:

我沒有從任何地方了解到的cl是不是class的簡寫,但這個方法確實是用來對“類”進行初

始化的。換句話說它是用來初始化static上下文的。

在類裝載(load)時,JVM會調用內置的方法對類成員和靜態初始化塊進行初始化調用。它們

的順序按照源文件的原文順序。

我們稍微增加兩行static語句:

packagedebug;

publicclassTest{

staticintx=0;

staticStrings="123";

static{

Strings1="456";

if(1==1)

thrownewRuntimeException();

}

publicstaticvoidmain(String[]args){

newTest();

}

}

然後進行反編譯:

javap-cdebug.Test

Compiledfrom"Test.java"

publicclassdebug.Testextendsjava.lang.Object{

staticintx;

staticjava.lang.Strings;

publicdebug.Test();

Code:

0:aload_0

1:invokespecial#1;//Methodjava/lang/Object."":()V

4:return

publicstaticvoidmain(java.lang.String[]);

Code:

0:new#2;//classdebug/Test

3:dup

4:invokespecial#3;//Method"":()V

7:pop

8:return

static{};

Code:

0:iconst_0

1:putstatic#4;//Fieldx:I

4:ldc#5;//String123

6:putstatic#6;//Fields:Ljava/lang/String;

9:ldc#7;//String456

11:astore_0

12:new#8;//classjava/lang/RuntimeException

15:dup

16:invokespecial#9;//Methodjava/lang/RuntimeException."":()V

19:athrow

}

這裏,我們不得不說,JDK在javap功能上的實現有壹個BUG。static段的16標號,那裏標識了異常

的位置發生在""方法中,而實際上這段程序運行時的輸出卻是:

java.lang.ExceptionInInitializerError

Causedby:java.lang.RuntimeException

atdebug.Test.(Test.java:8)

Exceptioninthread"main"

但我們總可以明白,類初始化正是按照源文件中定義的原文順序進行。先是聲明

staticintx;

staticjava.lang.Strings;

然後對intx和Strings進行賦值:

0:iconst_0

1:putstatic#4;//Fieldx:I

4:ldc#5;//String123

6:putstatic#6;//Fields:Ljava/lang/String;

執行初始化塊的Strings1="456";生成壹個RuntimeException拋

9:ldc#7;//String456

11:astore_0

12:new#8;//classjava/lang/RuntimeException

15:dup

16:invokespecial#9;//Methodjava/lang/RuntimeException."":()V

19:athrow

要明白的是,""方法不僅是類初始化方法,而且也是接口初始化方法。並不是所以接口

的屬性都是內聯的,只有直接賦常量值的接口常量才會內聯。而

[publicstaticfinal]doubled=Math.random()*100;

這樣的表達式是需要計算的,在接口中就要由""方法來初始化。

下面我們再來看看實例初始化方法""

""用於對象創建時對對象進行初始化,當在HEAP中創建對象時,壹旦在HEAP分配了空間。最

先就會調用""方法。這個方法包括實例變量的賦值(聲明不在其中)和初始化塊,以及構造

方法調用。如果有多個重載的構造方法,每個構造方法都會有壹個對應的""方法。

同樣,實例變量和初始化塊的順序也是按源文件的原文順序執行,構造方法中的代碼在最後執行:

packagedebug;

publicclassTest{

intx=0;

Strings="123";

{

Strings1="456";

//if(1==1)

//thrownewRuntimeException();

}

publicTest(){

Stringss="789";

}

publicstaticvoidmain(String[]args){

newTest();

}

}

javap-cdebug.Test的結果:

Compiledfrom"Test.java"

publicclassdebug.Testextendsjava.lang.Object{

intx;

java.lang.Strings;

publicdebug.Test();

Code:

0:aload_0

1:invokespecial#1;//Methodjava/lang/Object."":()V

4:aload_0

5:iconst_0

6:putfield#2;//Fieldx:I

9:aload_0

10:ldc#3;//String123

12:putfield#4;//Fields:Ljava/lang/String;

15:ldc#5;//String456

17:astore_1

18:ldc#6;//String789

20:astore_1

21:return

publicstaticvoidmain(java.lang.String[]);

Code:

0:new#7;//classdebug/Test

3:dup

4:invokespecial#8;//Method"":()V

7:pop

8:return

}

如果在同壹個類中,壹個構造方法調用了另壹個構造方法,那麽對應的""方法就會調用另壹

個"",但是實例變量和初始化塊會被忽略,否則它們就會被多次執行。

packagedebug;

publicclassTest{

Strings1=rt("s1");

Strings2="s2";

publicTest(){

s1="s1";

}

publicTest(Strings){

this();

if(1==1)thrownewRuntime();

}

Stringrt(Strings){

returns;

}

publicstaticvoidmain(String[]args){

newTest("");

}

}

反編譯的結果:

Compiledfrom"Test.java"

publicclassdebug.Testextendsjava.lang.Object{

java.lang.Strings1;

java.lang.Strings2;

publicdebug.Test();

Code:

0:aload_0

1:invokespecial#1;//Methodjava/lang/Object."":()V

4:aload_0

5:aload_0

6:ldc#2;//Strings1

8:invokevirtual#3;//Methodrt:(Ljava/lang/String;)Ljava/lang/String;

11:putfield#4;//Fields1:Ljava/lang/String;

14:aload_0

15:ldc#5;//Strings2

17:putfield#6;//Fields2:Ljava/lang/String;

20:aload_0

21:ldc#2;//Strings1

23:putfield#4;//Fields1:Ljava/lang/String;

26:return

publicdebug.Test(java.lang.String);

Code:

0:aload_0

1:invokespecial#7;//Method"":()V

4:new#8;//classjava/lang/RuntimeException

7:dup

8:invokespecial#9;//Methodjava/lang/RuntimeException."":()V

11:athrow

java.lang.Stringrt(java.lang.String);

Code:

0:aload_1

1:areturn

publicstaticvoidmain(java.lang.String[]);

Code:

0:new#10;//classdebug/Test

3:dup

4:ldc#11;//String

6:invokespecial#12;//Method"":(Ljava/lang/String;)V

9:pop

10:return

}

我們再壹次看到了javap實現的bug,雖然有壹個"":(Ljava/lang/String;)V簽名可以說明

每個構造方法對應壹個不同,但Runtime異常仍然被定位到了""()V的方法中:

invokespecial#8;//Methodjava/lang/RuntimeException."":()V,而在main方法中的

調用卻明明是"":(Ljava/lang/String;)V.

但是我們看到,由於Test(Strings)調用了Test();所以"":(Ljava/lang/String;)V不再對

實例變量和初始化塊進次初始化:

publicdebug.Test(java.lang.String);

Code:

0:aload_0

1:invokespecial#7;//Method"":()V

4:new#8;//classjava/lang/RuntimeException

7:dup

8:invokespecial#9;//Methodjava/lang/RuntimeException."":()V

11:athrow

而如果兩個構造方法是相互獨立的,則每個構造方法調用前都會執行實例變量和初始化塊的調用:

packagedebug;

publicclassTest{

Strings1=rt("s1");

Strings2="s2";

{

Strings3="s3";

}

publicTest(){

s1="s1";

}

publicTest(Strings){

if(1==1)

thrownewRuntimeException();

}

Stringrt(Strings){

returns;

}

publicstaticvoidmain(String[]args){

newTest("");

}

}

反編譯的結果:

Compiledfrom"Test.java"

publicclassdebug.Testextendsjava.lang.Object{

java.lang.Strings1;

java.lang.Strings2;

publicdebug.Test();

Code:

0:aload_0

1:invokespecial#1;//Methodjava/lang/Object."":()V

4:aload_0

5:aload_0

6:ldc#2;//Strings1

8:invokevirtual#3;//Methodrt:(Ljava/lang/String;)Ljava/lang/String;

11:putfield#4;//Fields1:Ljava/lang/String;

14:aload_0

15:ldc#5;//Strings2

17:putfield#6;//Fields2:Ljava/lang/String;

20:ldc#7;//Strings3

22:astore_1

23:aload_0

24:ldc#2;//Strings1

26:putfield#4;//Fields1:Ljava/lang/String;

29:return

publicdebug.Test(java.lang.String);

Code:

0:aload_0

1:invokespecial#1;//Methodjava/lang/Object."":()V

4:aload_0

5:aload_0

6:ldc#2;//Strings1

8:invokevirtual#3;//Methodrt:(Ljava/lang/String;)Ljava/lang/String;

11:putfield#4;//Fields1:Ljava/lang/String;

14:aload_0

15:ldc#5;//Strings2

17:putfield#6;//Fields2:Ljava/lang/String;

20:ldc#7;//Strings3

22:astore_2

23:new#8;//classjava/lang/RuntimeException

26:dup

27:invokespecial#9;//Methodjava/lang/RuntimeException."":()V

30:athrow

java.lang.Stringrt(java.lang.String);

Code:

0:aload_1

1:areturn

publicstaticvoidmain(java.lang.String[]);

Code:

0:new#10;//classdebug/Test

3:dup

4:ldc#11;//String

6:invokespecial#12;//Method"":(Ljava/lang/String;)V

9:pop

10:return

}