二維碼
        企資網(wǎng)

        掃一掃關(guān)注

        當(dāng)前位置: 首頁(yè) » 企業(yè)資訊 » 熱點(diǎn) » 正文

        一題搞定static關(guān)鍵字

        放大字體  縮小字體 發(fā)布日期:2021-09-05 06:33:58    作者:企資小編    瀏覽次數(shù):66
        導(dǎo)讀

        基礎(chǔ)不牢,地動(dòng)山搖。大家好,我是課代表。可以關(guān)注我的公眾號(hào):Java課代表,原創(chuàng)實(shí)戰(zhàn)干貨首發(fā)地兒,等你呦。開篇一道題,考察代碼執(zhí)行順序:public class Parent {static {System.out.println("Parent static i

        基礎(chǔ)不牢,地動(dòng)山搖。

        大家好,我是課代表。可以關(guān)注我的公眾號(hào):Java課代表,原創(chuàng)實(shí)戰(zhàn)干貨首發(fā)地兒,等你呦。

        開篇一道題,考察代碼執(zhí)行順序:

        public class Parent {    static {        System.out.println("Parent static initial block");    }    {        System.out.println("Parent initial block");    }    public Parent() {        System.out.println("Parent constructor block");    }}public class Child extends Parent {    static {        System.out.println("Child static initial block");    }    {        System.out.println("Child initial block");    }        private Hobby hobby = new Hobby();    public Child() {        System.out.println("Child constructor block");    }}public class Hobby {    static{        System.out.println("Hobby static initial block");    }    public Hobby() {        System.out.println("hobby constructor block");    }}

        當(dāng)執(zhí)行new Child()時(shí),上述代碼輸出什么?

        相信有不少同學(xué)遇到過(guò)這類問(wèn)題,可能查過(guò)資料之后接著就忘了,再次遇到還是答不對(duì)。接下來(lái)課代表通過(guò)4個(gè)步驟,帶大家拆解一下這段代碼的執(zhí)行順序,并借此總結(jié)規(guī)律。

        1.編譯器優(yōu)化了啥?

        下面兩段代碼對(duì)比一下編譯前后的變化:

        編譯前的Child.java

        public class Child extends Parent {    static {        System.out.println("Child static initial block");    }    {        System.out.println("Child initial block");    }        private Hobby hobby = new Hobby();        public Child() {        System.out.println("Child constructor block");    }}

        編譯后的Child.class

        public class Child extends Parent {    private Hobby hobby;    public Child() {        System.out.println("Child initial block");        this.hobby = new Hobby();        System.out.println("Child constructor block");    }    static {        System.out.println("Child static initial block");    }}

        通過(guò)對(duì)比可以看到,編譯器把初始化塊和實(shí)例字段的賦值操作,移動(dòng)到了構(gòu)造函數(shù)代碼之前,并且保留了相關(guān)代碼的先后順序。事實(shí)上,如果構(gòu)造函數(shù)有多個(gè),初始化代碼也會(huì)被復(fù)制多份移動(dòng)過(guò)去。

        據(jù)此可以得出第一條優(yōu)先級(jí)順序:

      1. 初始化代碼 > 構(gòu)造函數(shù)代碼

        2.static 有啥作用?

        類的加載過(guò)程可粗略分為三個(gè)階段:加載 -> 鏈接 -> 初始化

        初始化階段可被8種情況觸發(fā):

        1. 使用 new 關(guān)鍵字實(shí)例化對(duì)象的時(shí)候
        2. 讀取或設(shè)置一個(gè)類型的靜態(tài)字段(常量除外)
        3. 調(diào)用一個(gè)類型的靜態(tài)方法
        4. 使用反射調(diào)用類的時(shí)候
        5. 當(dāng)初始化類的時(shí)候,如果發(fā)現(xiàn)父類還沒(méi)有進(jìn)行過(guò)初始化,則先觸發(fā)其父類初始化
        6. 虛擬機(jī)啟動(dòng)時(shí),會(huì)先初始化主類(包含main()方法的那個(gè)類)
        7. 當(dāng)初次調(diào)用 MethodHandle 實(shí)例時(shí),初始化該 MethodHandle 指向的方法所在的類。
        8. 如果接口中定義了默認(rèn)方法(default 修飾的接口方法),該接口的實(shí)現(xiàn)類發(fā)生了初始化,則該接口要在其之前被初始化

        其中的2,3條目是被static代碼觸發(fā)的。

        其實(shí)初始化階段就是執(zhí)行類構(gòu)造器<clinit> 方法的過(guò)程,這個(gè)方法是編譯器自動(dòng)生成的,里面收集了static修飾的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{} 塊),并且保留這些代碼出現(xiàn)的先后順序。

        根據(jù)條目5,JVM 會(huì)保證在子類的<clinit>方法執(zhí)行前,父類的<clinit>方法已經(jīng)執(zhí)行完畢。

        小結(jié)一下:訪問(wèn)類變量或靜態(tài)方法,會(huì)觸發(fā)類的初始化,而類的初始化就是執(zhí)行<clinit>,也就是執(zhí)行 static 修飾的賦值動(dòng)作和static{}塊,并且 JVM 保證先執(zhí)行父類初始化,再執(zhí)行子類初始化。

        由此得出第二條優(yōu)先級(jí)順序:

      2. 父類的static代碼 > 子類的static代碼

        3.static 代碼只執(zhí)行一次

        我們都知道,static代碼(靜態(tài)方法除外)只執(zhí)行一次。

        你有沒(méi)有想過(guò),這個(gè)機(jī)制是如何保證的呢?

        答案是:雙親委派模型。

        JDK8 及之前的雙親委派模型是:

        應(yīng)用程序類加載器 → 擴(kuò)展類加載器 → 啟動(dòng)類加載器

        平時(shí)開發(fā)中寫的類,默認(rèn)都是由 應(yīng)用程序類加載器加載,它會(huì)委派給其父類:擴(kuò)展類加載器。而擴(kuò)展類加載器又會(huì)委派給其父類:?jiǎn)?dòng)類加載器。只有當(dāng)父類加載器反饋無(wú)法完成這個(gè)加載請(qǐng)求時(shí),子加載器才會(huì)嘗試自己去完成加載,這個(gè)過(guò)程就是雙親委派。三者的父子關(guān)系并不是通過(guò)繼承,而是通過(guò)組合模式實(shí)現(xiàn)的。

        該過(guò)程的實(shí)現(xiàn)也很簡(jiǎn)單,下面展示關(guān)鍵實(shí)現(xiàn)代碼:

        protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{    // 首先檢查該類是否被加載過(guò)    // 如果加載過(guò),直接返回該類    Class<?> c = findLoadedClass(name);    if (c == null) {        try {            if (parent != null) {                c = parent.loadClass(name, false);            } else {                c = findBootstrapClassOrNull(name);            }        } catch (ClassNotFoundException e) {            // 如果父類拋出ClassNotFoundException            // 說(shuō)明父類無(wú)法完成加載請(qǐng)求        }        if (c == null) {            // 如果父類無(wú)法加載,轉(zhuǎn)由子類加載            c = findClass(name);        }    }    if (resolve) {        resolveClass(c);    }    return c;}

        結(jié)合注釋相信大家很容易看懂。

        由雙親委派的代碼可知,同一個(gè)類加載器下,一個(gè)類只能被加載一次,也就限定了它只能被初始化一次。所以類中的 static代碼(靜態(tài)方法除外)只在類初始化時(shí)執(zhí)行一次

        4. <init>和<clinit>

        前面已經(jīng)介紹了編譯器自動(dòng)生成的類構(gòu)造器:<clinit>方法,它會(huì)收集static修飾的所有類變量的賦值動(dòng)作和靜態(tài)語(yǔ)句塊(static{} 塊)并保留代碼的出現(xiàn)順序,它會(huì)在類初始化時(shí)執(zhí)行

        相應(yīng)的,編譯器還會(huì)生成一個(gè)<init>方法,它會(huì)收集實(shí)例字段的賦值動(dòng)作、初始化語(yǔ)句塊({}塊)和構(gòu)造器(Constructor)中的代碼,并保留代碼的出現(xiàn)順序,它會(huì)在 new 指令之后接著執(zhí)行

        所以,當(dāng)我們new 一個(gè)類時(shí),如果JVM未加載該類,則先對(duì)其進(jìn)行初始化,再進(jìn)行實(shí)例化。

        至此,第三條優(yōu)先級(jí)規(guī)則也就呼之欲出了:

      3. 靜態(tài)代碼(static{}塊、靜態(tài)字段賦值語(yǔ)句) > 初始化代碼({}塊、實(shí)例字段賦值語(yǔ)句)

        5. 規(guī)律實(shí)踐

        將前文的三條規(guī)則合并,總結(jié)出如下兩條:

        1.靜態(tài)代碼(static{}塊、靜態(tài)字段賦值語(yǔ)句) > 初始化代碼({}塊、實(shí)例字段賦值語(yǔ)句) > 構(gòu)造函數(shù)代碼

        2.父類的static代碼 > 子類的static代碼

        根據(jù)前文總結(jié),初始化代碼和構(gòu)造函數(shù)代碼被編譯器收集到了<init>中,靜態(tài)代碼被收集到了<clinit>中,所以再次對(duì)上述規(guī)律做合并:

        父類<clinit> > 子類<clinit> > 父類 <init> > 子類 <init>

        對(duì)應(yīng)到開篇的問(wèn)題,我們來(lái)實(shí)踐一下:

        當(dāng)執(zhí)行new Child()時(shí),new關(guān)鍵字觸發(fā)了 Child 類的初始化 ,JVM 發(fā)現(xiàn)其有父類,則先初始化 Parent 類,開始執(zhí)行Parent類的<clinit>方法,然后執(zhí)行 Child 類的<clinit>方法(還記得<clinit>里面收集了什么嗎?)。

        然后開始實(shí)例化 一個(gè)Child類的對(duì)象,此時(shí)準(zhǔn)備執(zhí)行 Child 的<init>方法,發(fā)現(xiàn)它有父類,優(yōu)先執(zhí)行父類的<init>方法,然后再執(zhí)行子類的<init>(還記得<init>里面收集了什么嗎?)。

        相信看到這里,各位心中已經(jīng)對(duì)開篇的問(wèn)題有答案了,不妨先手寫一下輸出順序,然后寫代碼親自驗(yàn)證一下。

        結(jié)束語(yǔ)

        平時(shí)開發(fā)中經(jīng)常用到static,每次寫的時(shí)候,心里總會(huì)打兩個(gè)問(wèn)號(hào),我為什么要用static?不用行不行?這正應(yīng)了開篇的第一句話:

        基礎(chǔ)不牢,地動(dòng)山搖

        通過(guò)本文可以看出,static的應(yīng)用遠(yuǎn)遠(yuǎn)不止類變量,靜態(tài)方法那么簡(jiǎn)單。在經(jīng)典的單例模式中,你將看到static的各種用法,下一篇就寫如何花式編寫單例模式。


        原創(chuàng)碼字不容易,歡迎關(guān)注點(diǎn)贊和分享。

      4.  
        (文/企資小編)
        免責(zé)聲明
        本文僅代表作發(fā)布者:企資小編個(gè)人觀點(diǎn),本站未對(duì)其內(nèi)容進(jìn)行核實(shí),請(qǐng)讀者僅做參考,如若文中涉及有違公德、觸犯法律的內(nèi)容,一經(jīng)發(fā)現(xiàn),立即刪除,需自行承擔(dān)相應(yīng)責(zé)任。涉及到版權(quán)或其他問(wèn)題,請(qǐng)及時(shí)聯(lián)系我們刪除處理郵件:weilaitui@qq.com。
         

        Copyright ? 2016 - 2025 - 企資網(wǎng) 48903.COM All Rights Reserved 粵公網(wǎng)安備 44030702000589號(hào)

        粵ICP備16078936號(hào)

        微信

        關(guān)注
        微信

        微信二維碼

        WAP二維碼

        客服

        聯(lián)系
        客服

        聯(lián)系客服:

        在線QQ: 303377504

        客服電話: 020-82301567

        E_mail郵箱: weilaitui@qq.com

        微信公眾號(hào): weishitui

        客服001 客服002 客服003

        工作時(shí)間:

        周一至周五: 09:00 - 18:00

        反饋

        用戶
        反饋

        主站蜘蛛池模板: 国产乱码精品一区二区三区中| 一区精品麻豆入口| 在线播放精品一区二区啪视频| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 一区二区三区电影在线观看| 亚洲线精品一区二区三区影音先锋| 精品国产日韩一区三区| 国产手机精品一区二区| 蜜臀AV在线播放一区二区三区| 综合久久一区二区三区 | 一本岛一区在线观看不卡| 亚洲精品精华液一区二区 | 欧洲精品免费一区二区三区| 91香蕉福利一区二区三区| 久久高清一区二区三区| 亚洲中文字幕无码一区| 熟女性饥渴一区二区三区| 亚洲男人的天堂一区二区| 国产激情一区二区三区成人91| 视频一区二区精品的福利| 精品一区二区三区在线视频观看 | 亚洲av日韩综合一区在线观看| 国产精品xxxx国产喷水亚洲国产精品无码久久一区 | 一区二区视频在线| 色狠狠AV一区二区三区| 无码一区二区三区| 国产经典一区二区三区蜜芽| 91成人爽a毛片一区二区| 精品欧洲AV无码一区二区男男| 激情内射亚洲一区二区三区 | 国产乱码精品一区二区三区四川| 日韩精品免费一区二区三区| 国产主播一区二区三区| 小泽玛丽无码视频一区| 中文国产成人精品久久一区| 国产剧情国产精品一区| 亚洲乱码一区av春药高潮| 久久精品国内一区二区三区| 国产在线精品一区二区三区直播| 免费无码一区二区三区蜜桃| 中文字幕精品无码一区二区三区|