java提高之靜態代碼塊、非靜態代碼塊、靜態方法、構造方法、構造代碼塊

有過java基礎的同學肯定繞不開這個小山坡,靜態代碼塊、非靜態代碼塊、靜態方法、構造方法、構造代碼塊,這些,哪些先執行,哪些後執行,爲什麼呢?

首先,先說下論點,再用code佐證,最後探討機制。

java類加載順序

1、虛擬機在首次加載Java類時,會對靜態初始化塊、靜態成員變量、靜態方法進行一次初始化 2、只有在調用new方法時纔會創建類的實例 3、類實例創建過程:按照父子繼承關係進行初始化,首先執行父類的初始化塊部分,然後是父類的構造方法;再執行本類繼承的子類的初始化塊,最後是子類的構造方法 4、類實例銷燬時候,首先銷燬子類部分,再銷燬父類部分

非靜態代碼塊是在每次實例化對象時被調用的。new對象之後發生的事情:給對象的實例變量(非“常量”)分配內存空間,默認初始化成員變量;成員變量聲明時的初始化;初始化塊初始化(又稱爲構造代碼塊或非靜態代碼塊);構造方法初始化。

示例代碼

public class Test01 {
    public static void main(String[] args) {
        B b = new B();
    }
}
class B extends A{
    static{
        System.out.println("子類的靜態代碼塊");
    }
    C c = new C();
    {
        System.out.println("子類的構造代碼塊");
        C.method();
    }
    B(){
        System.out.println("子類的構造方法");
    }
}
class A{
    static{
        System.out.println("父類的靜態代碼塊");
    }
    C c = new C();
    A(){
        System.out.println("父類的構造方法");
    }
}
class C{
    static public  void method(){
        System.out.println("C的靜態方法");
    }
    static{
        System.out.println("C類的靜態代碼塊");
    }
    C(){
        System.out.println("C的構造方法");
    }
}

代碼運行的結果:

父類的靜態代碼塊
子類的靜態代碼塊
C類的靜態代碼塊
C的構造方法
父類的構造方法
C的構造方法
子類的構造代碼塊
C的靜態方法
子類的構造方法

解讀:
首次加載Java類時,會對靜態初始化塊、靜態成員變量、靜態方法進行一次初始化,而B繼承A,本來要先執行B的靜態代碼塊,但按照父子關係初始化,A加了個塞,先執行A的靜態代碼塊,打印“父類的靜態代碼塊”;B的靜態代碼塊也加載,打印“子類的靜態代碼塊”;在父類中把C類也加載進來了,靜態代碼塊執行,打印“C類的靜態代碼塊”,C在new的時候,構造方法執行,打印“C的構造方法”;然後執行到A的構造方法,打印“父類的構造方法”;接着在B類繼續向下執行,又new了個C,打印“C的構造方法”,然後向下執行非靜態代碼塊,打印“子類的構造代碼塊”以及“C的靜態方法”;最後B類的構造方法執行,打印“子類的構造方法”。

機制

具體內存中如何加載,暫時還沒時間看,TODO,等看到java的類加載機制再詳細探討。

根據反編譯的結果來看,整個運行過程是這樣的:

public class Test01 {
    public static void main(String[] args) {
        B b = new B();
    }
}
class B extends A{
    static{
        System.out.println("子類的靜態");
    }
    C c ;

    B(){
        super();
        c = new C();
        System.out.println("子類的構造代碼塊");
        C.method();
        System.out.println("子類的構造方法");
    }
    public void method(){
        System.out.println("子類的成員方法");
    }
}
class C{
    static public  void method(){
        System.out.println("C的靜態方法");
    }
    static{
        System.out.println("C類的靜態");
    }
    C(){
        super();

        System.out.println("C的構造方法");
    }
}
class A{
    static{
        System.out.println("父類的靜態");
    }
    C c ;
    A(){
        super();
        c = new C();
        System.out.println("父類的構造方法");
    }
}

構造方法裏面,先super(),再成員變量(c=new C()),再其他代碼,操作完畢,依次讀取。這就是內存中真實的運行規則。

擴展

看下面的題,請問輸出的是什麼

public class Test03 {
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}
class Zi extends Fu{
    public int count=3;
    public Zi(){
    }
    public void print(){
        System.out.println("Zi..."+count);
    }
}
class Fu{
    public int count=10;
    public Fu(){
        print();
    }
    public void print(){
        System.out.println("Fu..."+count);
    }
}

同之前的邏輯,改造後代碼如下:

public class Test03 {
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}
class Zi extends Fu{
    public int count=0;//默認初始值爲0
    public Zi(){
        super();
        count=3;
    }
    public void print(){
        System.out.println("Zi..."+count);
    }
}
class Fu{
    public int count=0;
    public Fu(){
        super();
        count=10;
        print();
    }
    public void print(){
        System.out.println("Fu..."+count);
    }
}

Zi zi = new Zi(),過去找子類的構造方法,super(),此時子類的count還是0,找父類的構造方法,父類,的count改爲10,print(),子類自己有重寫,找子類自己的,count也是找子類自己的,此時還是0,結果Zi...0

真實JVM中運行情況

類加載過程分爲:加載、驗證、準備、解析、初始化這幾個階段。其中準備階段類變量(static修飾的變量)分配內存並設置類變量的初始值一般爲零值,final修飾的直接賦值);初始化階段是執行類構造器<clinit>()方法的過程,包括類中所有類變量的賦值動作靜態代碼塊。虛擬機保證子類<clinit>()之前,父類該方法執行完畢。父類靜態代碼塊優先於子類變量賦值動作。實例化過程一般都清楚。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章