一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|

服務器之家 - 編程語言 - JAVA教程 - Java類初始化和實例化中的2個“雷區”

Java類初始化和實例化中的2個“雷區”

2020-03-31 11:46Zerohuan JAVA教程

這篇文章主要介紹了Java類初始化和實例化中的2個“雷區”,大家要注意,感興趣的小伙伴們可以參考一下

在考慮類初始化時,我們都知道進行子類初始化時,如果父類沒有初始化要先初始化子類。然而事情并沒有一句話這么簡單。
首先看看Java中初始化觸發的條件:

(1)在使用new實例化對象,訪問靜態數據和方法時,也就是遇到指令:new,getstatic/putstatic和invokestatic時;
(2)使用反射對類進行調用時;
(3)當初始化一個類時,父類如果沒有進行初始化,先觸發父類的初始化;
(4)執行入口main方法所在的類;
(5)JDK1.7動態語言支持中方法句柄所在的類,如果沒有初始化觸發起初始化;

經過編譯后生成一個<clinit>方法,類的初始化就在這個方法中進行,該方法只執行,由JVM保證這一點,并進行同步控制;
其中條件(3),從方法調用的角度來看,是子類的<clinit>會在開始時遞歸的調用父類的<clinit>,這類似與我們在子類構造器中必須首先調用父類的構造器;
但需要注意的是“觸發”并不是完成初始化,這意味著有可能子類的初始化會提前于父類初始化結束,這就是“危險”的所在。

1. 一個類初始化的例子:
這個例子我使用一個外圍類包含2個有繼承關系的靜態成員類,因為外圍類的初始化和靜態成員類沒有因果關系,因此這樣展示是安全和方便的;
父類A和子類B分別包含main函數,由上面的觸發條件(4)可知,通過分別調用這個兩個main函數來觸發不同的類初始化路徑;
這個例子的問題在于父類包含子類的static引用并在定義處進行初始化的問題:

?
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
public class WrapperClass {
  private static class A {
    static {
      System.out.println("類A初始化開始...");
    }
    //父類包含子類的static引用
    private static B b = new B();
    protected static int aInt = 9;
 
    static {
      System.out.println("類A初始化結束...");
    }
 
    public static void main(String[] args) {
 
    }
  }
 
  private static class B extends A {
    static {
      System.out.println("類B初始化開始...");
    }
    //子類的域依賴于父類的域
    private static int bInt = 9 + A.aInt;
 
    public B() {
      //構造器依賴類的static域
      System.out.println("類B的構造器調用 " + "bInt的值" + bInt);
    }
 
    static {
      System.out.println("類B初始化結束... " + "aInt的值:" + bInt);
    }
 
    public static void main(String[] args) {
 
    }
  }
}

情景一:入口為類B的main函數時輸出結果:

?
1
2
3
4
5
6
7
/**
   * 類A初始化開始...
   * 類B的構造器調用 bInt的值0
   * 類A初始化結束...
   * 類B初始化開始...
   * 類B初始化結束... aInt的值:18
   */

分析:可以看到,main函數的調用觸發了類B的初始化,進入類B的<clinit>方法,類A作為其父類先開始初始化進入了A的<clinit>方法,其中有一個語句new B();這時會進行B的實例化,這是已經在類B的<clinit>中了,main線程已經獲得鎖開始執行類B的<clinit>,我們開頭說過JVM會保證一個類的初始化方法只被執行一次,JVM收到new指令后不會再進入類B的<clinit>方法而是直接進行實例化,但是此時類B還沒有完成類初始化,所以可以看到bInt的值為0(這個0是類加載中準備階段分配方法區內存后進行的置零初始化);
因此,可以得出,再父類中包含子類類型的static域并進行賦值動作,會可能導致子類實例化在類初始化完成前進行;

情景二:入口為類A的main函數時輸出結果:

?
1
2
3
4
5
6
7
/**
   * 類A初始化開始...
   * 類B初始化開始...
   * 類B初始化結束... aInt的值:9
   * 類B的構造器調用 bInt的值9
   * 類A初始化結束...
   */

分析:經過情景一的分析,我們知道,由類B的初始化觸發類A的初始化,會導致類A中類變量b的實例化在類B初始化完成前進行,那如果先初始化類A是不是就可以在類變量實例化的時候先觸發類B的初始化,從而使得初始化在實例化前呢?答案是肯定的,但是這仍然有問題。
根據輸出,可以看到,類B的初始化在類A的初始化完成前進行了,這導致了像類變量aInt的變量在類B初始化完成后才進行初始化,所以類B中的域bInt獲取到的aInt的值是“0”,而不是我們預期的“18”;

結論:綜上,可以得出,在父類中包含子類類型的類變量,并在定義出進行實例化是非常危險的行為,具體情況可能不會向例子一樣直白,調用方法在定義處賦值一樣隱含著危險,即使要包含子類類型的static域,也應該通過static方法進行賦值,因為JVM可以保證在static方法調用前完成所有的初始化動作(當然這種保證也是你不應該包含static B b = new B();這樣的初始化行為);

2. 一個實例化的例子:
首先需要知道對象創建的過程:
(1)遇到new指令,檢查類是否完成了加載,驗證,準備,解析,初始化(解析過程就是符號引用解析成直接引用,比如方法名就是一個符號引用,可以在初始化完成后使用這個符號引用的時候進行,正是為了支持動態綁定),沒有完成先進行這些過程;
(2)分配內存,采用空閑列表或者指針碰撞的方法,并將新分配的內存“置零”,因此所有的實例變量在此環節都進行了一次默認初始化為0(引用為null)的過程;
(3)執行<init>方法,包括檢查調用父類的<init>方法(構造器),實例變量定義出的賦值動作,實例化器順序執行,最后調用構造器中的動作。

這個例子可能更為大家所熟知,也就是它違反了“不要在構造器,clone方法和readObject方法中調用可被覆蓋的方法”。其原因就在于Java中的多態,也就是動態綁定。
父類A的構造器中包含一個protected方法,類B是其子類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class WrongInstantiation {
  private static class A {
    public A() {
      doSomething();
    }
 
    protected void doSomething() {
      System.out.println("A's doSomething");
    }
  }
 
  private static class B extends A {
    private int bInt = 9;
 
    @Override
    protected void doSomething() {
      System.out.println("B's doSomething, bInt: " + bInt);
    }
  }
 
  public static void main(String[] args) {
    B b = new B();
  }
}

輸出結果:

?
1
2
3
/**
   * B's doSomething, bInt: 0
   */

分析:首先需要知道,在沒有顯示提供構造器時Java編譯器會生成默認構造器,并在開始處調用父類的構造器,因此類B的構造器開始會先調用類A的構造器。
類A中調用了protected方法doSomething,從輸出結果中我們看到實際上調用的是子類的方法實現,而此時子類的實例化還未開始,因此bInt并沒有如“預期”那樣是9,而是0;
這就是由于動態綁定,doSomething是一個protected方法,因此它是通過invokevirtual指令調用的,該指令根據對象實例的類型找到對應的方法實現(這里就是B的實例對象,對應方法就是類B的方法實現)執行,故而有此結果。

結論:正如前面說的“不要在構造器,clone方法和readObject方法中調用可被覆蓋的方法”。

以上就是為大家介紹的Java類初始化和實例化中的2個“雷區”,希望對大家的學習有所幫助。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 草逼吧| 亚洲社区在线观看 | 日韩精品 欧美 | 九九艹 | 欧美精品1区 | 我半夜摸妺妺的奶C了她 | 毛片网站大全 | 免费黄色片网站 | www亚洲色图 | 色天天综合色天天碰 | 国产综合久久久久久 | 草莓永久地域网名入2022 | 亚洲天堂h | 全黄h全肉细节修仙玄幻文 全彩调教侵犯h本子全彩妖气he | 国产成人精品高清免费 | 青青草原在线 | 男gaygays免费网站多人 | 国产综合欧美日韩视频一区 | 国内自拍2020 | 91庥豆果冻天美精东蜜桃传媒 | 日日操天天射 | 青青久久久国产线免观 | 青青青手机在线观看 | 男人扒开女人下身添 | 99热久久这里只有精品6国产网 | 掀开奶罩边躁狠狠躁软学生 | 99国产牛牛视频在线网站 | 91插插插插 | yellow高清免费观看日本 | 免费看视频网站 | 青青草色 | 国产拍拍视频一二三四区 | 亚洲高清国产品国语在线观看 | 国产成人精品一区二区 | uoco福利姬网站 | 青青青久热国产精品视频 | 亚洲高清影院 | 果冻传媒和91制片厂网站软件 | 免费的网址 | 男人天堂999 | 国产午夜永久福利视频在线观看 |