1. 程式人生 > >Java類載入與例項化過程

Java類載入與例項化過程

0x00 背景知識

1、虛擬機器在首次載入Java類時,會對靜態初始化塊、靜態成員變數、靜態方法(下文將這三種統稱為靜態程式碼塊)進行一次初始化
具體過程是:
①裝(加)載類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區中,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構,之後可以用Class物件進行相關的反射操作。
②連線分為三個子步驟    

    驗證:確保被載入的類的正確性

    準備:為類的靜態變數分配記憶體,並將其初始化為預設值

    解析: 把類中的符號引用轉換為直接引用

③初始化為為類的靜態變數賦予正確的初始值

2、類例項建立過程:按照父子繼承關係進行初始化,首先執行父類的初始化塊部分,然後是父類的構造方法;再執行本類繼承的子類的初始化塊,最後是子類的構造方法
3、類例項銷燬時候,首先銷燬子類部分,再銷燬父類部分

成員變數和靜態變數的區別:
1,成員變數所屬於物件。所以也稱為例項變數。
靜態變數所屬於類。所以也稱為類變數。
2,成員變數存在於堆記憶體中。
靜態變數存在於方法區中。
3,成員變數隨著物件建立而存在。隨著物件被回收而消失。
靜態變數隨著類的載入而存在。隨著類的消失而消失。
4,成員變數只能被物件所呼叫 。
靜態變數可以被物件呼叫,也可以被類名呼叫。
所以,成員變數可以稱為物件的特有資料,靜態變數稱為物件的共享資料。

靜態程式碼塊:就是一個有靜態關鍵字標示的一個程式碼塊區域。定義在類中。
作用:可以完成類的初始化。靜態程式碼塊隨著類的載入而執行,而且只執行一次(new 多個物件就只執行一次)。如果和主函式在同一類中,優先於主函式執行。(靜態成員就是靜態程式碼塊)

//靜態程式碼塊:在java中使用static關鍵字宣告的程式碼塊。靜態塊用於初始化類,為類的屬性初始化。每個靜態程式碼塊只會執行一次。由於JVM在載入類時會執行靜態程式碼塊,所以靜態程式碼塊先於主方法執行。
//如果類中包含多個靜態程式碼塊,那麼將按照"先定義的程式碼先執行,後定義的程式碼後執行"。
//注意:1 靜態程式碼塊不能存在於任何方法體內。2 靜態程式碼塊不能直接訪問靜態例項變數和例項方法,需要通過類的例項物件來訪問。
class Code{ { System.out.println("Code的構造塊"); } static{ System.out.println("Code的靜態程式碼塊"); } public Code(){ System.out.println("Code的構造方法"); } } public class CodeBlock03{ { System.out.println("CodeBlock03的構造塊"); } static{ System.out.println("CodeBlock03的靜態程式碼塊"); } public CodeBlock03(){ System.out.println("CodeBlock03的構造方法"); } public static void main(String[] args){ System.out.println("CodeBlock03的主方法"); new Code(); new Code(); new CodeBlock03(); new CodeBlock03(); } } /* CodeBlock03的靜態程式碼塊 CodeBlock03的主方法 Code的靜態程式碼塊 Code的構造塊 Code的構造方法 Code的構造塊 Code的構造方法 CodeBlock03的構造塊 CodeBlock03的構造方法 CodeBlock03的構造塊 CodeBlock03的構造方法 */

構造程式碼塊:類中的獨立程式碼塊,與物件相關,物件呼叫一次就執行一次。
作用:給所有的物件進行初始化。

//構造塊:直接在類中定義且沒有加static關鍵字的程式碼塊稱為{}構造程式碼塊。構造程式碼塊在建立物件時被呼叫,每次建立物件都會被呼叫,並且構造程式碼塊的執行次序優先於類建構函式。

public class CodeBlock02{
    {
      System.out.println("第一程式碼塊");    
    }

    public CodeBlock02(){
        System.out.println("構造方法");
        }

        {
          System.out.println("第二構造塊");
        }
      public static void main(String[] args){
          new CodeBlock02();
          new CodeBlock02();
          new CodeBlock02();

    }
}    

/*
*
執行結果:
第一程式碼塊
第二構造塊
構造方法
第一程式碼塊
第二構造塊
構造方法
第一程式碼塊
第二構造塊
構造方法
*/

0x01先來幾個小測試:

測試一

class Singleton {
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    private Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstance() {
        return sin;
    }
}

public class Test {
    public static void main(String[] args) {
        Singleton sin = Singleton.getInstance();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

測試二:

class Singleton {   
    public static int counter1;
    public static int counter2 = 0;
    private static Singleton sin = new Singleton();

    private Singleton() {
        counter1++;
        counter2++;
    }

    public static Singleton getInstance() {
        return sin;
    }
}

public class Test {
    public static void main(String[] args) {
        Singleton sin = Singleton.getInstance();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

測試三:

class Singleton {
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    public Singleton() {
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

測試四:

class Singleton {

    public static int counter1;
    public static int counter2 = 0;
    private static Singleton sin = new Singleton();

    {
        counter1=3;
    }

    public Singleton() {
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

結果:
測試一:1,0
測試二:1,1
測試三:2,1
測試四:4,2

0x02類載入

測試一分析:
按類載入的流程走一遍:
①裝(加)載類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區中,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構,之後可以用Class物件進行相關的反射操作。(都一樣)
②連線分為三個子步驟 
驗證:確保被載入的類的正確性
準備:為類的靜態變數分配記憶體,並將其初始化為預設值
測試一中:sin=NULL;counter1=0;counter2=0;
解析: 把類中的符號引用轉換為直接引用
③初始化為為類的靜態變數賦予正確的初始值
測試一中第一句:
private static Singleton sin = new Singleton();//執行後counter1=1;counter2=2;
第二句:public static int counter1;//執行後counter1=1;
第二句:public static int counter2 = 0;//執行counter2=0;
所以最後結果為1,0
測試二同理。

0x03類的例項化

類例項建立過程簡單總結一下就是(new一個類的時候到底發生了什麼?):
預設初始化(對成員賦0或NULL)—>進建構函式—>super()【父類也是該過程】—>顯式初始化—>構造程式碼塊—>建構函式中其它程式碼

class Singleton {

    /*
     *靜態程式碼塊(靜態成員)
     */
    private static Singleton sin = new Singleton();
    public static int counter1;
    public static int counter2 = 0;

    /*
     *構造程式碼塊
     */
    {
        counter1=3;
    }

    /*
     *造函式
     */
    public Singleton() {
        //類Singleton 的super class為object
        super();

        //建構函式中其它程式碼
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        Singleton sin = new Singleton();
        System.out.println(sin.counter1);
        System.out.println(sin.counter2);
    }
}   

根據上面總結的流程走一遍:

類載入階段:跟測試一一樣,該階段結束後counter1=1;counter2=0;
類例項化階段:
預設初始化(對成員賦0NULL//沒有普通成員
—>進建構函式//開始進入Singleton()
—>super()【父類也是該過程】//進入super(),即Object載入
—>顯式初始化//super()執行完後開始顯示為成員賦值,本測試中沒有普通的非static成員
—>構造程式碼塊 //沒有
—>建構函式中其它程式碼//       counter1++;counter2++;結束時counter1=2,counter2=1

測試四也走一遍:

類載入階段:跟測試一一樣,該階段結束後counter1=1;counter2=1;
類例項化階段:
預設初始化(對成員賦0NULL//沒有普通成員
—>進建構函式//開始進入Singleton()
—>super()【父類也是該過程】//進入super(),即Object載入
—>顯式初始化//super()執行完後開始顯示為成員賦值,本測試中沒有普通的非static成員
—>構造程式碼塊 //counter1=3;
—>建構函式中其它程式碼//       counter1++;counter2++;結束時counter1=4,counter2=2

0x04 自測題

public class Farther
{
    static
    {
        System.out.println("靜態程式碼塊      farther");
    }

    {
        System.out.println("構造程式碼塊  farther");
    }
    public Farther()
    {
        System.out.println("call farther counter1="+Singleton.counter1+"  counter2="+Singleton.counter2);

        Singleton.counter1++;
    }

}

class Singleton extends Farther {   

    static
    {
        System.out.println("靜態程式碼塊1  counter1="+Singleton.counter1 +"  counter2="+Singleton.counter2);
    }

    public static int counter1;
    public static int counter2 = 1;

    static
    {
        System.out.println("靜態程式碼塊2  counter1="+counter1 +"  counter2="+counter2);
    }

    private static Singleton sin = new Singleton();
    static
    {
        System.out.println("靜態程式碼塊3  counter1="+counter1 +"  counter2="+counter2);
    }

    {
        counter1++;
        System.out.println("構造程式碼塊  counter1="+counter1+"  counter2="+counter2);
    }

    public Singleton() {
        System.out.println("call Singleton");
        counter1++;
        counter2++;
    }

}

public class Test {
    public static void main(String[] args) {
        //Singleton sin = Singleton.getInstance();
        Singleton sin = new Singleton();
        System.out.println("counter1="+sin.counter1);
        System.out.println("counter2="+sin.counter2);

    }
}
/*
靜態程式碼塊      farther
靜態程式碼塊1  counter1=0  counter2=0
靜態程式碼塊2  counter1=0  counter2=1
構造程式碼塊  farther
call farther counter1=0  counter2=1
構造程式碼塊  counter1=2  counter2=1
call Singleton
靜態程式碼塊3  counter1=3  counter2=2
構造程式碼塊  farther
call farther counter1=3  counter2=2
構造程式碼塊  counter1=5  counter2=2
call Singleton
counter1=6
counter2=3

*/

執行過程就是:
1.先載入 Farther
列印了:“靜態程式碼塊 farther”
2.後加載Singleton
先列印了:“靜態程式碼塊1 counter1=0 counter2=0“
”靜態程式碼塊2 counter1=0 counter2=1”
靜態程式碼塊: private static Singleton sin = new Singleton();呼叫了Singleton的建構函式,
先執行super(),所以“call farther counter1=0 counter2=1”被列印;
再執行構造程式碼塊,所以“構造程式碼塊 counter1=2 counter2=1”被列印
再執行建構函式中其他的程式碼,所以“call Singleton”被列印,建構函式呼叫結束。
接著執行靜態程式碼塊,所以“靜態程式碼塊3 counter1=3 counter2=2”被列印。
3.在main函式中呼叫Singleton sin = new Singleton();
預設初始化
—>進建構函式
—>super()【父類也是該過程,可看做該過程的一次遞迴】,//所以“構造程式碼塊 farther”和“call farther counter1=3 counter2=2”被列印
—>顯式初始化
—>構造程式碼塊 //所以“構造程式碼塊 counter1=5 counter2=2”被列印
—>建構函式中其它程式碼 //所以“call Singleton”被列印
最後列印:counter1=6
counter2=3