類的初始化過程
基本概念
類加載:在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;
}
}