類的初始化過程


類的初始化過程

基本概念

類加載:在java代碼中,類型(class,enum,interface)的加載、連接和初始化過程都是在程序運行期間完成的。這樣提供了更大的靈活性,增加了更多的可能性
類加載器:JAVA源程序=》javac編譯=》字節碼文件.class=》JVM=》裝載ClassLoader=》運行時數據區=》執行引擎,本地方法庫接口=====》本地方法庫
JVM基本結構:類加載器,執行引擎,運行時數據區,本地接口
類的裝載:
  加載:查找並加載類的二進制數據
    類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,然后在內存中創建一個java.lang.Class對象(規范並未說明Class對象位於哪里,在HotSpot虛擬機將其放在了方法區中)用來封裝類在方法區內的數據結構
  連接
    驗證:確保被加載的類的正確性
    准備:為類的靜態變量分配內存,並將其初始化為默認值
    解析:把類中的符號引用轉換為直接引用
  初始化:為類的靜態變量賦予正確的初始值
    Java虛擬機實現必須在每個類或接口被Java程序首次主動使用時才初始化它們。(對於什么時候類加載,java虛擬機規范中並沒有進行強制約束,這點可以交給虛擬機的具體實現來自由把我,但是對於初始化階段,虛擬機嚴格對定了以下幾種情況)
    JAVA程序對類的使用方式可分為兩種:主動使用(六種),被動使用
    主動使用
     1)創建類的實例,也就是new Object
     2)訪問某個或接口的靜態變量,或者對靜態變量賦值
     3)訪問類的靜態方法
     4)反射調用類
     5)初始化一個類的子類
     6)Java虛擬機啟動時被表明為啟動類的類(如一個類中含有main方法)
    其他使用java類的方式都被看作為被動使用

主動使用樣列

1、初始化一個類的子類回先初始化它的父類,但是初始化父類,子類並不會被初始化,需要牢牢把握主動使用的六種

public class Test1 {
    public static void main(String[] args) {
        //當只執行下面這條語句的時候,子類的static靜態代碼塊並不會運行,也就是沒有初始化,
        //因為對於類的初始化只有首次主動使用時才會初始化
        /**
         * 輸出
         * MyParent1 static block
         * hello world
         */
         System.out.println(MyChild1.str);
//        System.out.println("=======================================");
//        System.out.println(MyChild1.str2);
    }

}
class MyParent1 {
    public static String str = "hello world";
    static {
        System.out.println("MyParent1 static block");
    }
}
class MyChild1 extends MyParent1 {
    public static String str2 = "welcome";
    static {
        System.out.println("MyChild1 static block");
    }
}

2、對於final常量的初始化過程

/**
 * 常量在編譯階段會存入到調用這個常量的方法所在類的常量池中,
 * 本質上,調用類並沒有直接引用到定義常量的類,因此並不會觸發定義常量的類的初始化
 * 注意:這里指的時將常量存存放到Test2的常量池中,之后Test2與MyParent2就沒有任何關系了,
 *      甚至我們可以將MyParent2的Class文件刪除
 */
public class Test2 {
    public static void main(String[] args) {
        //不會初始化MyParent2類
        System.out.println(MyParent2.str); //這里只會輸出hellword,並不會導致MyParent2被初始化
    }
}
class MyParent2 {
    public static final String str = "hello world";
    static {
        System.out.println("MyParent2 static block");
    }
}
/**
 * 當一個常量的值並非編譯器期間不可以確定的,那么其值就不會被放到調用該類的常量池中
 * 這時在程序運行時,會導致主動使用這個常量所在的類,顯然會導致這個類被除初始化
 */
public class Test3 {
    public static void main(String[] args) {
        //由於str的值是編譯期不能確定值的,所以會導致MyParent3被初始化
        System.out.println(MyParent3.str);//這里就回導致MyParent3被初始化
    }
}
class MyParent3 {
    //該值是在編譯期間是不知道的,只有當運行時才能確定
    public static final String str = UUID.randomUUID().toString();
    static {
        System.out.println("MyParent3 static code");
    }
}

3、對於數組的初始化過程

/**
 * 對於數組實例來說,其類型是由JVM在運行期動態生成的,表示為[Lcom.chen.jvm.MyParent4
 * 這種形式。動態生成的類型,其父類型就是Object
 */
/**
 * 對於數組的加載javadoc
 * 數組類的加載器
 * <p> <tt>Class</tt> objects for array classes are not created by class
 * loaders, but are created automatically as required by the Java runtime.
 * The class loader for an array class, as returned by {@link
 * Class#getClassLoader()} is the same as the class loader for its element
 * type; if the element type is a primitive type, then the array class has no
 * class loader.
 */
public class Test4 {
    public static void main(String[] args) {
        //並不是主動使用,也就不會導致MyParent4類的初始化
        MyParent4[] myParent4 = new MyParent4[1];
        //[Lcom.chen.jvm.MyParent4;  這個類型是java虛擬機幫助我們動態生成的類型
        System.out.println(myParent4.getClass());//class [Lcom.chen.jvm.MyParent4;
        System.out.println(myParent4.getClass().getSuperclass());//class java.lang.Object
        MyParent4 myParent5 = new MyParent4();//MyParent4 statci code
        System.out.println(myParent5.getClass());//class com.chen.jvm.MyParent4

        int[] ints = new int[1];
        System.out.println(ints.getClass());//class [I
        System.out.println(ints.getClass().getSuperclass());//class java.lang.Object
    }
}
class MyParent4 {
    static {
        System.out.println("MyParent4 statci code");
    }
}

4、接口父子初始化過程

/**
 * 當一個接口初始化時,並不要求其父接口都完成初始化
 * 只有在真正使用到父接口的時刻(如引用接口中所定義的常量時),才會初始化
 */
public class Test5 {
    public static void main(String[] args) {
        //也就是MyParent5接口並不會被初始化
        System.out.println(MyChild5.b); //6
    }
}
interface MyParent5 {
    static int a = 5;
}
interface MyChild5 extends MyParent5 {
    static int b = 6;
}

5、靜態變量初始化過程

public class Test6 {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        System.out.println(Singleton.counter1); // 輸出1
        //若new Singleton()放在counter1於counter2下面的話,也會輸出1
        //這個實例可以典型說明類的初始化過程是先將靜態變量賦予默認值,然后再賦正確的值這一過程
        System.out.println(Singleton.counter2); // 輸出0
    }
}
class Singleton {
    private static Singleton singleton = new Singleton();
    public static int counter1;
    public static int counter2 = 0;
//    private static Singleton singleton = new Singleton();
    public Singleton() {
        counter1++;
        counter2++;
    }

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM