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

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