java中類的初始化和對象的初始化


靜態的屬於全局靜態區,這個部分獨立存在,不管是成員還是塊,都是靜態的,大家地位相等,先到先得。

  然后是成員初始化,這個部分在類的構造函數前初始化,因為編譯器(或者設計者)可能認為構造函數調用了這個成員變量,所以在其前初始化了。或者說是成員自己有自己的能力來初始化自己,不用構造函數來管,這部分屬於能力比較強的,雖然沒有靜態的地位高,但是,還是能自給自足的一部分團體。

  最后才構造函數,這個時候就開始動工來建立這個類的實例了。這個步驟相當於建大樓,終於開始建立了,前面的准備工作已經准備完了,下面就開始對那些沒有自理能力的成員進行初始化等。

  對於繼承的時候,先父類,后子類。同樣理解,靜態的地位還是依舊高,高於一切。然后再是有自理能力的成員變量,最后才構造函數來處理小兵小將。

  注意:構造函數是先與“成員變量初始化”,也就是調用構造函數時會先調用父類(super())的構造函數,接着頂層的構造函數會先執行其成員變量的初始化,然后構造函數里其余的代碼被執行,一層一層向下執行。

  java初始化順序

  1 無繼承情況下的Java初始化順序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class  Sample
{
Sample(String s)
{
System.out.println(s);
}
Sample()
{
System.out.println( "Sample默認構造函數被調用" );
}
}
class  Test{
static  Sample sam= new  Sample( "靜態成員sam初始化" );
Sample sam1= new  Sample( "sam1成員初始化" );
static {
System.out.println( "static塊執行" );
if (sam== null )System.out.println( "sam is null" );
sam= new  Sample( "靜態塊內初始化sam成員變量" );
}
Test()
{
System.out.println( "Test默認構造<span id=" 24_nwp " style=" width: auto; height: auto;  float : none; "><a id=" 24_nwl " href=" http: //cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=%BA%AF%CA%FD&k0=%BA%AF%CA%FD&kdi0=0&luki=8&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target="_blank" mpid="24" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">函數</span></a></span>被調用");
}
}
//主函數
public  static  void  main(String str[])
{
Test a= new  Test();
}

  輸出結果為:

  靜態成員sam初始化 -----靜態成員初始化

  static塊執行 -----靜態塊被執行

  靜態塊內初始化sam成員變量 ----靜態塊執行

  sam1成員初始化 -----普通成員初始化

  Test默認構造函數被調用 -----構造函數執行

  由此可以得出結論:

  a 靜態成員變量首先初始化(注意,Static可以看做一個靜態成員,其執行順序和其在類中申明的順序有關)

  b 普通成員初始化

  c 執行構造函數

  對於靜態成員(static塊可以看成普通的一個靜態成員,其並不一定在類初始化時首先執行)和普通成員,其初始化順序只與其在類定義中的順序有關,和其他因素無關。

  例如下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class  Test{
static {
System.out.println( "static 塊 1 執行" );
}
static  Sample staticSam1= new  Sample( "靜態成員staticSam1初始化" );
Sample sam1= new  Sample( "sam1成員初始化" );
static  Sample staticSam2= new  Sample( "靜態成員staticSam2初始化" );
static {
System.out.println( "static 塊 2 執行" );
}
Test()
{
System.out.println( "Test默認構造函數被調用" );
}
Sample sam2= new  Sample( "sam2成員初始化" );
}

  則結果為:

  static 塊 1 執行

  靜態成員staticSam1初始化

  靜態成員staticSam2初始化

  static 塊 2 執行

  --------靜態成員

  sam1成員初始化

  sam2成員初始化

  --------普通成員

  Test默認構造函數被調用

  --------構造函數

  2 Java繼承情況下的初始化順序:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class  Test{
  static {
 System.out.println( "父類static 塊 1 執行" );
 }
  static  Sample staticSam1= new  Sample( "父類 靜態成員staticSam1初始化" );
 Sample sam1= new  Sample( "父類 sam1成員初始化" );
  static  Sample staticSam2= new  Sample( "父類 靜態成員staticSam2初始化" );
  static {
 System.out.println( "父類 static 塊 2 執行" );
 }
 Test()
 {
 System.out.println( "父類 Test默認構造<span id=" 21_nwp " style=" width: auto; height: auto;  float : none; "><a id=" 21_nwl " href=" http: //cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=%BA%AF%CA%FD&k0=%BA%AF%CA%FD&kdi0=0&luki=8&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target="_blank" mpid="21" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">函數</span></a></span>被調用");
 }
 Sample sam2= new  Sample( "父類 sam2成員初始化" );
 }
  class  TestSub  extends  Test
 {
  static  Sample staticSamSub= new  Sample( "子類 靜態成員staticSamSub初始化" );
 TestSub()
 {
 System.out.println( "子類 TestSub 默認構造函數被調用" );
 }
 Sample sam1= new  Sample( "子類 sam1成員初始化" );
  static  Sample staticSamSub1= new  Sample( "子類 靜態成員staticSamSub1初始化" );
  static {System.out.println( "子類 static 塊 執行" );}
 Sample sam2= new  Sample( "子類 sam2成員初始化" );
 }

  執行結果:

  父類 static 塊 1 執行

  父類 靜態成員staticSam1初始化

  父類 靜態成員staticSam2初始化

  父類 static 塊 2 執行

  --------父類靜態成員初始化

  子類 靜態成員staticSamSub初始化

  子類 靜態成員staticSamSub1初始化

  子類 static 塊 執行

  -------子類靜態成員初始化

  父類 sam1成員初始化

  父類 sam2成員初始化

  父類 Test默認構造函數被調用

  -------父類普通成員初始化和構造函數執行

  子類 sam1成員初始化

  子類 sam2成員初始化

  子類 TestSub 默認構造函數被調用

  -------父類普通成員初始化和構造函數執行

  由此得出Java初始化順序結論:

  1 繼承體系的所有靜態成員初始化(先父類,后子類)

  2 父類初始化完成(普通成員的初始化-->構造函數的調用)

  3 子類初始化(普通成員-->構造函數)

  Java初始化順序如圖:

1.png

  Java 類和對象的初始化過程探索

  類的初始化和對象初始化是 JVM 管理的類型生命周期中非常重要的兩個環節,Google 了一遍網絡,有關類裝載機制的文章倒是不少,然而類初始化和對象初始化的文章並不多,特別是從字節碼和 JVM 層次來分析的文章更是鮮有所見。本文主要對類和對象初始化全過程進行分析,通過一個實際問題引入,將源代碼轉換成 JVM 字節碼后,對 JVM 執行過程的關鍵點進行全面解析,並在文中穿插入了相關 JVM 規范和 JVM 的部分內部理論知識,以理論與實際結合的方式介紹對象初始化和類初始化之間的協作以及可能存在的沖突問題。

  問題引入

  近日我在調試一個枚舉類型的解析器程序,該解析器是將數據庫內一萬多條枚舉代碼裝載到緩存中,為了實現快速定位枚舉代碼和具體枚舉類別的所有枚舉元素,該類在裝載枚舉代碼的同時對其采取兩種策略建立內存索引。由於該類是一個公共服務類,在程序各個層面都會使用到它,因此我將它實現為一個單例類。這個類在我調整類實例化語句位置之前運行正常,但當我把該類實例化語句調整到靜態初始化語句之前時,我的程序不再為我工作了。

  下面是經過我簡化后的示例代碼:

  [清單一]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package  com.ccb.framework.enums;
import  <span id= "18_nwp"  style= "width: auto; height: auto; float: none;" ><a id= "18_nwl"  href= "http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target= "_blank"  mpid= "18"  style= "text-decoration: none;" ><span style= "color:#0000ff;font-size:12px;width:auto;height:auto;float:none;" >java</span></a></span>.util.Collections;
import  java.util.HashMap;
import  java.util.Map;
public  class  CachingEnumResolver {
//單態實例 一切問題皆由此行引起
private  static  final  CachingEnumResolver SINGLE_ENUM_RESOLVER =  new
CachingEnumResolver();
/*MSGCODE->Category內存索引*/
private static Map CODE_MAP_CACHE;
static {
CODE_MAP_CACHE = new HashMap();
//為了說明問題,我在這里初始化一條數據
CODE_MAP_CACHE.put("0","北京市");
}
//private, for single instance
private CachingEnumResolver() {
//初始化加載數據 引起問題,該方法也要負點責任
initEnums();
}
/**
* 初始化所有的枚舉類型
*/
public static void initEnums() {
// ~~~~~~~~~問題從這里開始暴露 ~~~~~~~~~~~//
if (null == CODE_MAP_CACHE) {
System.out.println("CODE_MAP_CACHE為空,問題在這里開始暴露.");
CODE_MAP_CACHE = new HashMap();
}
CODE_MAP_CACHE.put("1", "北京市");
CODE_MAP_CACHE.put("2", "雲南省");
//..... other code...
}
public Map getCache() {
return Collections.unmodifiableMap(CODE_MAP_CACHE);
}
/**
* 獲取單態實例
*
* @return
*/
public  static  CachingEnumResolver getInstance() {
return  SINGLE_ENUM_RESOLVER;
}
public  static  void  main(String[] args) {
System.out.println(CachingEnumResolver.getInstance().getCache());
}
}

  想必大家看了上面的代碼后會感覺有些茫然,這個類看起來沒有問題啊,這的確屬於典型的餓漢式單態模式啊,怎么會有問題呢?

  是的,他看起來的確沒有問題,可是如果將他 run 起來時,其結果是他不會為你正確 work。運行該類,它的執行結果是:

  [清單二]

1
2
CODE_MAP_CACHE為空,問題在這里開始暴露.
{ 0 =北京市}

  我的程序怎么會這樣?為什么在 initEnum() 方法里 CODE_MAP_CACHE 為空?為什么我輸出的 CODE_MAP_CACHE 內容只有一個元素,其它兩個元素呢????!!

  看到這里,如果是你在調試該程序,你此刻一定覺得很奇怪,難道是我的 Jvm 有問題嗎?非也!如果不是,那我的程序是怎么了?這絕對不是我想要的結果。可事實上無論怎么修改 initEnum() 方法都無濟於事,起碼我最初是一定不會懷疑到問題可能出在創建 CachingEnumResolver 實例這一環節上。正是因為我太相信我創建 CachingEnumResolver 實例的方法,加之對 Java 類初始化與對象實例化底層原理理解有所偏差,使我為此付出了三、四個小時--約半個工作日的大好青春。

  那么問題究竟出在哪里呢?為什么會出現這樣的怪事呢?在解決這個問題之前,先讓我們來了解一下JVM的類和對象初始化的底層機制。

  類的生命周期

  上圖展示的是類生命周期流向;在本文里,我只打算談談類的"初始化"以及"對象實例化"兩個階段。

  類初始化

  類"初始化"階段,它是一個類或接口被首次使用的前階段中的最后一項工作,本階段負責為類變量賦予正確的初始值。

  Java 編譯器把所有的類變量初始化語句和類型的靜態初始化器通通收集到 方法內,該方法只能被 Jvm 調用,專門承擔初始化工作。

  除接口以外,初始化一個類之前必須保證其直接超類已被初始化,並且該初始化過程是由 Jvm 保證線程安全的。另外,並非所有的類都會擁有一個 () 方法,在以下條件中該類不會擁有 () 方法:

  該類既沒有聲明任何類變量,也沒有靜態初始化語句;該類聲明了類變量,但沒有明確使用類變量初始化語句或靜態初始化語句初始化;該類僅包含靜態 final 變量的類變量初始化語句,並且類變量初始化語句是編譯時常量表達式。對象初始化

  在類被裝載、連接和初始化,這個類就隨時都可能使用了。對象實例化和初始化是就是對象生命的起始階段的活動,在這里我們主要討論對象的初始化工作的相關特點。

  Java 編譯器在編譯每個類時都會為該類至少生成一個實例初始化方法--即 "()" 方法。此方法與源代碼中的每個構造方法相對應,如果類沒有明確地聲明任何構造方法,編譯器則為該類生成一個默認的無參構造方法,這個默認的構造器僅僅調用父類的無參構造器,與此同時也會生成一個與默認構造方法對應的 "()" 方法.

  通常來說,() 方法內包括的代碼內容大概為:調用另一個 () 方法;對實例變量初始化;與其對應的構造方法內的代碼。

  如果構造方法是明確地從調用同一個類中的另一個構造方法開始,那它對應的 () 方法體內包括的內容為:一個對本類的 () 方法的調用;對應用構造方法內的所有字節碼。

  如果構造方法不是通過調用自身類的其它構造方法開始,並且該對象不是 Object 對象,那 () 法內則包括的內容為:一個對父類 () 方法的調用;對實例變量初始化方法的字節碼;最后是對應構造子的方法體字節碼。

  如果這個類是 Object,那么它的 () 方法則不包括對父類 () 方法的調用。

  類的初始化時機

  本文到目前為止,我們已經大概有了解到了類生命周期中都經歷了哪些階段,但這個類的生命周期的開始階段--類裝載又是在什么時候被觸發呢?類又是何時被初始化的呢?讓我們帶着這三個疑問繼續去尋找答案。

  Java 虛擬機規范為類的初始化時機做了嚴格定義:"initialize on first active use"--" 在首次主動使用時初始化"。這個規則直接影響着類裝載、連接和初始化類的機制--因為在類型被初始化之前它必須已經被連接,然而在連接之前又必須保證它已經被裝載了。

  在與初始化時機相關的類裝載時機問題上,Java 虛擬機規范並沒有對其做嚴格的定義,這就使得 JVM 在實現上可以根據自己的特點提供采用不同的裝載策略。我們可以思考一下 Jboss AOP 框架的實現原理,它就是在對你的 class 文件裝載環節做了手腳--插入了 AOP 的相關攔截字節碼,這使得它可以對程序員做到完全透明化,哪怕你用 new 操作符創建出的對象實例也一樣能被 AOP 框架攔截--與之相對應的 Spring AOP,你必須通過他的 BeanFactory 獲得被 AOP 代理過的受管對象,當然 Jboss AOP 的缺點也很明顯--他是和 JBOSS 服務器綁定很緊密的,你不能很輕松的移植到其它服務器上。嗯~……,說到這里有些跑題了,要知道 AOP 實現策略足可以寫一本厚厚的書了,嘿嘿,就此打住。

  說了這么多,類的初始化時機就是在"在首次主動使用時",那么,哪些情形下才符合首次主動使用的要求呢?

  首次主動使用的情形:

  創建某個類的新實例時--new、反射、克隆或反序列化;調用某個類的靜態方法時;使用某個類或接口的靜態字段或對該字段賦值時(final字段除外);調用Java的某些反射方法時初始化某個類的子類時在虛擬機啟動時某個含有main()方法的那個啟動類。除了以上幾種情形以外,所有其它使用JAVA類型的方式都是被動使用的,他們不會導致類的初始化。

  我的問題究竟出在哪里

  好了,了解了JVM的類初始化與對象初始化機制后,我們就有了理論基礎,也就可以理性的去分析問題了。

  下面讓我們來看看前面[清單一]的JAVA源代碼反組譯出的字節碼:

  [清單三]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
   public  class  com.ccb.framework.enums.CachingEnumResolver  extends
  <span id= "8_nwp"  style= "width: auto; height: auto; float: none;" ><a id= "8_nwl"  href= "http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target= "_blank"  mpid= "8"  style= "text-decoration: none;" ><span style= "color:#0000ff;font-size:12px;width:auto;height:auto;float:none;" >java</span></a></span>.lang.Object{
   static  {};
  Code:
   0 new  # 2 //class CachingEnumResolver
   3 : dup
   4 : invokespecial # 14 //Method "":()V ①
   7 : putstatic # 16 //Field
  SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver;
   10 new  # 18 //class HashMap ②
   13 : dup
   14 : invokespecial # 19 //Method java/util/HashMap."":()V
   17 : putstatic # 21 //Field CODE_MAP_CACHE:Ljava/util/Map;
   20 : getstatic # 21 //Field CODE_MAP_CACHE:L<span id="9_nwp" style="width: auto; height: auto; float: none;"><a id="9_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target="_blank" mpid="9" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">java</span></a></span>/util/Map;
   23 : ldc # 23 //String 0
   25 : ldc # 25 //String 北京市
   27 : invokeinterface # 31 3 //InterfaceMethod
  java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 
   32 : pop
   33 return
   private  com.ccb.framework.enums.CachingEnumResolver();
  Code:
   0 : aload_0
   1 : invokespecial # 34 //Method java/lang/Object."":()V
   4 : invokestatic # 37 //Method initEnums:()V ④
   7 return
   public  static  void  initEnums();
  Code:
   0 : getstatic # 21 //Field CODE_MAP_CACHE:L<span id="10_nwp" style="width: auto; height: auto; float: none;"><a id="10_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target="_blank" mpid="10" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">java</span></a></span>/util/Map; ⑤
   3 : ifnonnull  24
   6 : getstatic # 44 //Field java/lang/System.out:Ljava/io/PrintStream;
   9 : ldc # 46 //String CODE_MAP_CACHE為空,問題在這里開始暴露.
   11 : invokevirtual # 52 //Method
  java/io/PrintStream.println:(Ljava/lang/String;)V
   14 new  # 18 //class HashMap
   17 : dup
   18 : invokespecial # 19 //Method java/util/HashMap."":()V ⑥
   21 : putstatic # 21 //Field CODE_MAP_CACHE:Ljava/util/Map;
   24 : getstatic # 21 //Field CODE_MAP_CACHE:L<span id="11_nwp" style="width: auto; height: auto; float: none;"><a id="11_nwl" href="http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target="_blank" mpid="11" style="text-decoration: none;"><span style="color:#0000ff;font-size:12px;width:auto;height:auto;float:none;">java</span></a></span>/util/Map;
   27 : ldc # 54 //String 1
   29 : ldc # 25 //String 北京市
   31 : invokeinterface # 31 3 //InterfaceMethod
  java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; 
   36 : pop
   37 : getstatic # 21 //Field CODE_MAP_CACHE:Ljava/util/Map;
   40 : ldc # 56 //String 2
   42 : ldc # 58 //String 雲南省
   44 : invokeinterface # 31 3 //InterfaceMethod
  java/util/Map.put:(Ljava/lang/Object;L<span id= "12_nwp"  style= "width: auto; height: auto; float: none;" ><a id= "12_nwl"  href= "http://cpro.baidu.com/cpro/ui/uijs.php?adclass=0&app_id=0&c=news&cf=43&ch=0&di=128&fv=18&is_app=0&jk=6e43776a3c15f009&k=java&k0=java&kdi0=0&luki=10&n=10&p=baidu&q=xiaoxiaobai_cpr&rb=0&rs=1&seller_id=1&sid=9f0153c6a77436e&ssp2=1&stid=0&t=tpclicked3_hc&td=1972324&tu=u1972324&u=http%3A%2F%2Fwww%2E52ij%2Ecom%2Fjishu%2Fjava%2F98821%2Ehtml&urlid=0" target= "_blank"  mpid= "12"  style= "text-decoration: none;" ><span style= "color:#0000ff;font-size:12px;width:auto;height:auto;float:none;" >java</span></a></span>/lang/Object;)Ljava/lang/Object; 
   49 : pop
   50 return
   public  java.util.Map getCache();
  Code:
   0 : getstatic # 21 //Field CODE_MAP_CACHE:Ljava/util/Map;
   3 : invokestatic # 66 //Method
  java/util/Collections.unmodifiableMap:(Ljava/util/Map;)Ljava/util/Map;
   6 : areturn
   public  static  com.ccb.framework.enums.CachingEnumResolver 
getInstance();
  Code:
   0 : getstatic # 16 ;
   //Field SINGLE_ENUM_RESOLVER:Lcom/ccb/framework/enums/CachingEnumResolver; 
   3 : areturn
  }

  如果上面[清單一]顯示,清單內容是在 JDK1.4 環境下的字節碼內容,可能這份清單對於很大部分兄弟來說確實沒有多少吸引力,因為這些 JVM 指令確實不像源代碼那樣漂亮易懂。但它的的確確是查找和定位問題最直接的辦法,我們想要的答案就在這份 JVM 指令清單里。

  現在,讓我們對該類從類初始化到對象實例初始化全過程分析[清單一]中的代碼執行軌跡。

  如前面所述,類初始化是在類真正可用時的最后一項前階工作,該階段負責對所有類正確的初始化值,此項工作是線程安全的,JVM會保證多線程同步。

  第1步:調用類初始化方法 CachingEnumResolver.(),該方法對外界是不可見的,換句話說是 JVM 內部專用方法,() 內包括了 CachingEnumResolver 內所有的具有指定初始值的類變量的初始化語句。要注意的是並非每個類都具有該方法,具體的內容在前面已有敘述。

  第2步:進入 () 方法內,讓我們看字節碼中的 "①" 行,該行與其上面兩行組合起來代表 new 一個 CachingEnumResolver 對象實例,而該代碼行本身是指調用 CachingEnumResolver 類的 ()方法。每一個 Java 類都具有一個 () 方法,該方法是 Java 編譯器在編譯時生成的,對外界不可見,() 方法內包括了所有具有指定初始化值的實例變量初始化語句和java類的構造方法內的所有語句。對象在實例化時,均通過該方法進行初始化。然而到此步,一個潛在的問題已經在此埋伏好,就等着你來犯了。

  第3步:讓我們順着執行順序向下看,"④" 行,該行所在方法就是該類的構造器,該方法先調用父類的構造器 () 對父對象進行初始化,然后調用 CachingEnumResolver.initEnum() 方法加載數據。

  第4步:"⑤" 行,該行獲取 "CODE_MAP_CACHE" 字段值,其運行時該字段值為 null。注意,問題已經開始顯現了。(作為程序員的你一定是希望該字段已經被初始化過了,而事實上它還沒有被初始化)。通過判斷,由於該字段為 NULL,因此程序將繼續執行到 "⑥" 行,將該字段實例化為 HashMap()。

  第5步:在 "⑦"、"⑧" 行,其功能就是為 "CODE_MAP_CACHE" 字段填入兩條數據。

  第6步:退出對象初始化方法 (),將生成的對象實例初始化給類字段 "SINGLE_ENUM_RESOLVER"。(注意,此刻該對象實例內的類變量還未初始化完全,剛才由 () 調用 initEnum() 方法賦值的類變量 "CODE_MAP_CACHE" 是 () 方法還未初始化字段,它還將在后面的類初始化過程再次被覆蓋)。

  第7步:繼續執行 ()方法內的后繼代碼,"②" 行,該行對 "CODE_MAP_CACHE" 字段實例化為 HashMap 實例(注意:在對象實例化時已經對該字段賦值過了,現在又重新賦值為另一個實例,此刻,"CODE_MAP_CACHE"變量所引用的實例的類變量值被覆蓋,到此我們的疑問已經有了答案)。

  第8步:類初始化完畢,同時該單態類的實例化工作也完成。

  通過對上面的字節碼執行過程分析,或許你已經清楚了解到導致錯誤的深層原因了,也或許你可能早已被上面的分析過程給弄得暈頭轉向了,不過也沒折,雖然我也可以從源代碼的角度來闡述問題,但這樣不夠深度,同時也會有僅為個人觀點、不足可信之嫌。

  如何解決

  要解決上面代碼所存在的問題很簡單,那就是將 "SINGLE_ENUM_RESOLVER" 變量的初始化賦值語句轉移到 getInstance() 方法中去即可。換句話說就是要避免在類還未初始化完成時從內部實例化該類或在初始化過程中引用還未初始化的字段。

  寫在最后

  靜下浮燥之心,仔細思量自己是否真的掌握了本文主題所引出的知識,如果您覺得您已經完全或基本掌握了,那么很好,在最后,我將前面的代碼稍做下修改,請思考下面兩組程序是否同樣會存在問題呢?

  程序一

1
2
3
4
5
6
7
8
public  class  CachingEnumResolver {
public  static  Map CODE_MAP_CACHE;
static  {
CODE_MAP_CACHE =  new  HashMap();
//為了說明問題,我在這里初始化一條數據
CODE_MAP_CACHE.put( "0" , "北京市" );
initEnums();
}

  程序二

1
2
3
4
5
6
7
8
9
10
public  class  CachingEnumResolver {
private  static  final  CachingEnumResolver SINGLE_ENUM_RESOLVER;
public  static  Map CODE_MAP_CACHE;
static  {
CODE_MAP_CACHE =  new  HashMap();
//為了說明問題,我在這里初始化一條數據
CODE_MAP_CACHE.put( "0" , "北京市" );
SINGLE_ENUM_RESOLVER =  new  CachingEnumResolver();
initEnums();
}

  最后,一點關於 JAVA 群體的感言:時下正是各種開源框架盛行時期,Spring 更是大行其道,吸引着一大批 JEE 開發者的眼球(我也是 fans 中的一員)。然而,讓我們仔細觀察一下--以 Spring 群體為例,在那么多的 Spring fans 當中,有多少人去研究過 Spring 源代碼?又有多少人對 Spring 設計思想有真正深入了解呢?當然,我是沒有資格以這樣的口吻來說事的,我只是想表明一個觀點--學東西一定要"正本清源"。

  總結點評:

  1、非繼承類的初始化順序是:

  a 靜態成員變量首先初始化(注意,Static可以看做一個靜態成員,其執行順序和其在類中申明的順序有關)

  b 普通成員初始化

  c 執行構造函數

  2、這個程序用eclipse去debug一次,這個程序出問題的原因在於類初始化的時候靜態內容是按聲明的先后順序進行初始化的,初始化時先執行

1
2
private  static  final  CachingEnumResolver SINGLE_ENUM_RESOLVER =  new
CachingEnumResolver();

  這里的構造方法里面調用initEnums()方法,當運行到

1
2
3
4
if  ( null  == CODE_MAP_CACHE) {
System.out.println( "CODE_MAP_CACHE為空,問題在這里開始暴露." );
CODE_MAP_CACHE =  new  HashMap();
}

  時CODE_MAP_CACHE還未初始化,於是打印"CODE_MAP_CACHE為空,問題在這里開始暴露",然后往CODE_MAP_CACHE存值。

  接着程序執行

1
2
3
4
5
6
private  static  Map CODE_MAP_CACHE;
static  {
CODE_MAP_CACHE =  new  HashMap();
//為了說明問題,我在這里初始化一條數據
CODE_MAP_CACHE.put( "0" , "北京市" );
}

  CODE_MAP_CACHE 被重新分配了內存,於是CODE_MAP_CACHE 中又是空的,然后CODE_MAP_CACHE.put("0","北京市");//插入條數據。

  運行main的時候打印出的結果就成了“{0=北京市}”

  3、

1
2
     private  static  final  CachingEnumResolver SINGLE_ENUM_RESOLVER =  new
  \tCachingEnumResolver();

  這個在static}{之前執行,如果執行new CachingEnumResolver();此時不是還是要先初始化類??

  貌似這里很陷入一個陷阱里去了

  4、類的不妥的地方就是靜態代碼塊,以及那幾個靜態變量.因為它們本身是不屬於對象的,而是屬於類的.也就是在這個類的對象創建出來之前就會被得到執行賦值之類的,所以在:

  \tprivate static CachingEnumResolver SINGLE_ENUM_RESOLVER = new CachingEnumResolver();這一句的時候就是已經調用過了構造函數了,相當於這個對象已經被加載過了(而已經加載過的對象是會被緩存的.不會被重復調用,也就是說它的構造函數是不會被第二次調用).而繼續往下執行.就是執行下面的靜態代碼塊,又為CODE_MAP_CACHE這個變量賦值,導致覆

蓋。

 

 

來源:http://www.52ij.com/jishu/java/98821.html

 


免責聲明!

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



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