激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

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

服務(wù)器之家 - 編程語言 - JAVA教程 - Java類初始化和實(shí)例化中的2個(gè)“雷區(qū)”

Java類初始化和實(shí)例化中的2個(gè)“雷區(qū)”

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

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

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

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

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

1. 一個(gè)類初始化的例子:
這個(gè)例子我使用一個(gè)外圍類包含2個(gè)有繼承關(guān)系的靜態(tài)成員類,因?yàn)橥鈬惖某跏蓟挽o態(tài)成員類沒有因果關(guān)系,因此這樣展示是安全和方便的;
父類A和子類B分別包含main函數(shù),由上面的觸發(fā)條件(4)可知,通過分別調(diào)用這個(gè)兩個(gè)main函數(shù)來觸發(fā)不同的類初始化路徑;
這個(gè)例子的問題在于父類包含子類的static引用并在定義處進(jìn)行初始化的問題:

?
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初始化結(jié)束...");
    }
 
    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() {
      //構(gòu)造器依賴類的static域
      System.out.println("類B的構(gòu)造器調(diào)用 " + "bInt的值" + bInt);
    }
 
    static {
      System.out.println("類B初始化結(jié)束... " + "aInt的值:" + bInt);
    }
 
    public static void main(String[] args) {
 
    }
  }
}

情景一:入口為類B的main函數(shù)時(shí)輸出結(jié)果:

?
1
2
3
4
5
6
7
/**
   * 類A初始化開始...
   * 類B的構(gòu)造器調(diào)用 bInt的值0
   * 類A初始化結(jié)束...
   * 類B初始化開始...
   * 類B初始化結(jié)束... aInt的值:18
   */

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

情景二:入口為類A的main函數(shù)時(shí)輸出結(jié)果:

?
1
2
3
4
5
6
7
/**
   * 類A初始化開始...
   * 類B初始化開始...
   * 類B初始化結(jié)束... aInt的值:9
   * 類B的構(gòu)造器調(diào)用 bInt的值9
   * 類A初始化結(jié)束...
   */

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

結(jié)論:綜上,可以得出,在父類中包含子類類型的類變量,并在定義出進(jìn)行實(shí)例化是非常危險(xiǎn)的行為,具體情況可能不會(huì)向例子一樣直白,調(diào)用方法在定義處賦值一樣隱含著危險(xiǎn),即使要包含子類類型的static域,也應(yīng)該通過static方法進(jìn)行賦值,因?yàn)镴VM可以保證在static方法調(diào)用前完成所有的初始化動(dòng)作(當(dāng)然這種保證也是你不應(yīng)該包含static B b = new B();這樣的初始化行為);

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

這個(gè)例子可能更為大家所熟知,也就是它違反了“不要在構(gòu)造器,clone方法和readObject方法中調(diào)用可被覆蓋的方法”。其原因就在于Java中的多態(tài),也就是動(dòng)態(tài)綁定。
父類A的構(gòu)造器中包含一個(gè)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();
  }
}

輸出結(jié)果:

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

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

結(jié)論:正如前面說的“不要在構(gòu)造器,clone方法和readObject方法中調(diào)用可被覆蓋的方法”。

以上就是為大家介紹的Java類初始化和實(shí)例化中的2個(gè)“雷區(qū)”,希望對大家的學(xué)習(xí)有所幫助。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 一区二区三区日韩在线观看 | 精品国产一区二区亚洲人成毛片 | 曰韩在线视频 | 免费a级网站 | 欧美日日操 | 久久国产精品一区 | www.精品一区 | 黄色影院在线观看视频 | 久久99精品久久久久久园产越南 | 最近日本电影hd免费观看 | 成人偷拍片视频在线观看 | 国产精品久久久久久久亚洲按摩 | 国产污污视频 | 精品国产视频一区二区三区 | 日日爱影院 | 高清国产午夜精品久久久久久 | 欧美一级片一区 | 久久99国产伦子精品免费 | 欧美 日韩 国产 在线 | 亚洲精品久久久久久久久久久 | 中文字幕在线观看网址 | 国产免费人做人爱午夜视频 | 国产精品亚洲欧美 | 精品一区二区三区免费毛片爱 | 港台三级在线观看 | 久久国产精品久久久久 | 毛片网站网址 | 成人免费在线播放 | 性欧美极品xxxx欧美一区二区 | 斗罗破苍穹在线观看免费完整观看 | 亚洲视频成人在线 | 国产免费专区 | 久久久国产精品网站 | 他也色在线视频 | 99综合视频 | 精品一区二区三区免费毛片 | 免费毛片免费看 | 日本aⅴ在线 | 综合网日日天干夜夜久久 | 欧美性生活久久 | www.com国产精品 |