最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時間:2016年02月14日 17:06:24   作者:Zerohuan  
這篇文章主要介紹了Java類初始化和實例化中的2個“雷區(qū)”,大家要注意,感興趣的小伙伴們可以參考一下

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

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

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

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

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ù)時輸出結(jié)果:

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

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

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

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

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

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

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

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

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é)果:

/** 
   * B's doSomething, bInt: 0 
   */ 

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

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

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

相關(guān)文章

  • Spring Cloud構(gòu)建Eureka應(yīng)用的方法

    Spring Cloud構(gòu)建Eureka應(yīng)用的方法

    這篇文章主要介紹了Spring Cloud構(gòu)建Eureka應(yīng)用的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • 全面詳解java代碼重構(gòu)與設(shè)計模式

    全面詳解java代碼重構(gòu)與設(shè)計模式

    這篇文章主要為大家介紹了全面詳解java代碼重構(gòu)與設(shè)計模式的全面詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-06-06
  • Java實現(xiàn)獲取指定個數(shù)的不同隨機數(shù)

    Java實現(xiàn)獲取指定個數(shù)的不同隨機數(shù)

    今天小編就為大家分享一篇關(guān)于Java實現(xiàn)獲取指定個數(shù)的不同隨機數(shù),小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • Nacos負(fù)載均衡策略總結(jié)

    Nacos負(fù)載均衡策略總結(jié)

    Nacos 作為目前主流的微服務(wù)中間件,包含了兩個頂級的微服務(wù)功能:配置中心和注冊中心,本文給大家總結(jié)了幾種Nacos負(fù)載均衡策略,通過圖文結(jié)合介紹的非常詳細(xì),需要的朋友可以參考下
    2023-11-11
  • Java Comparable 和 Comparator 的詳解及區(qū)別

    Java Comparable 和 Comparator 的詳解及區(qū)別

    這篇文章主要介紹了Java Comparable 和 Comparator 的詳解及區(qū)別的相關(guān)資料,Comparable 自然排序和Comparator 定制排序的實例,需要的朋友可以參考下
    2016-12-12
  • IDEA 自動生成 JPA 實體類的圖文教程

    IDEA 自動生成 JPA 實體類的圖文教程

    這篇文章主要介紹了IDEA 自動生成 JPA 實體類的圖文教程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • 詳解SpringBoot如何正確解析日期格式

    詳解SpringBoot如何正確解析日期格式

    這篇文章主要介紹了SpringBoot如何正確解析日期格式,文中給出了兩種解決方案,通過代碼示例講解的非常詳細(xì),對大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-03-03
  • 解決idea找不到或無法加載主類的錯誤處理

    解決idea找不到或無法加載主類的錯誤處理

    這篇文章主要介紹了解決idea找不到或無法加載主類的錯誤處理,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • 關(guān)于Java中代碼塊的執(zhí)行順序

    關(guān)于Java中代碼塊的執(zhí)行順序

    這篇文章主要介紹了關(guān)于Java中代碼塊的執(zhí)行順序,構(gòu)造代碼塊是給所有對象進行統(tǒng)一初始化,而構(gòu)造函數(shù)是給對應(yīng)的對象初始化,因為構(gòu)造函數(shù)是可以多個的,運行哪個構(gòu)造函數(shù)就會建立什么樣的對象,但無論建立哪個對象,都會先執(zhí)行相同的構(gòu)造代碼塊,需要的朋友可以參考下
    2023-08-08
  • IDEA?隱藏DEBUG日志的解決方法

    IDEA?隱藏DEBUG日志的解決方法

    IDEA?打印太多的DEBUG日志,看起來很煩,有沒有辦法隱藏日志,網(wǎng)上找了一圈,沒有誰寫的靠譜的,下面小編給大家分享下IDEA?如何隱藏DEBUG日志,需要的朋友可以參考下
    2022-09-09

最新評論

鲁甸县| 绥阳县| 瑞金市| 三门峡市| 贵南县| 隆昌县| 和田市| 黔西| 南京市| 浦北县| 桃源县| 汕头市| 高雄县| 社旗县| 崇文区| 临颍县| 洮南市| 华蓥市| 土默特左旗| 边坝县| 保靖县| 泽库县| 霍城县| 淮滨县| 西充县| 通榆县| 滁州市| 疏勒县| 融水| 札达县| 民县| 永川市| 房产| 芦山县| 晴隆县| 教育| 永川市| 徐闻县| 永平县| 东安县| 赣榆县|