# 類載入 ## **基本說明** 反射機制是Java實現動態語言的關鍵,也就是通過反射實現類動態載入。 1. **靜態載入:**編譯時載入相關的類,如果沒有則報錯,依賴性太強。 2. **動態載入:**運行時載入相關的類,如果運行時不用該類,即使不存在該類,也不會報錯,降低了依賴性。 - **代碼 ...
類載入
基本說明
反射機制是Java實現動態語言的關鍵,也就是通過反射實現類動態載入。
- 靜態載入:編譯時載入相關的類,如果沒有則報錯,依賴性太強。
- 動態載入:運行時載入相關的類,如果運行時不用該類,即使不存在該類,也不會報錯,降低了依賴性。
-
代碼演示:
import java.util.*; import java.lang.reflect.*; public class ClassLoad_{ public static void main(String[] args) throws Exception{ Scanner scan = new Scanner(System.in); String key = scan.next(); switch(key){ case "1": Dog dog = new Dog();//靜態載入,依賴性很強 dog.cry(); break; case "2": //反射 -> 動態載入 Class cls = Class.forName("Person");//載入Person類[動態載入] Object o = cls.newInstance(); Method m = cls.getMethod("hi"); m.invoke(o); System.out.println("OK"); break; default: System.out.println("do nothing...."); } } } /* 未寫Dog類時,DOS視窗運行結果,編譯錯誤 C:\Users\86199\Desktop>javac ClassLoad_.java ClassLoad_.java:11: 錯誤: 找不到符號 Dog dog = new Dog();//靜態載入,依賴性很強 ^ 符號: 類 Dog 位置: 類 ClassLoad_ ClassLoad_.java:11: 錯誤: 找不到符號 Dog dog = new Dog();//靜態載入,依賴性很強 ^ 符號: 類 Dog 位置: 類 ClassLoad_ 註: ClassLoad_.java使用了未經檢查或不安全的操作。 註: 有關詳細信息, 請使用 -Xlint:unchecked 重新編譯。 2 個錯誤 */ //因為 new Dog() 是靜態載入,因此必須編寫Dog類,否則在編譯過程就會報錯 //Person類是動態載入,所以沒有編寫Person類在編譯過程也不會報錯, //只有在動態載入該類時才會報錯 class Dog{ public void cry(){ System.out.println("小狗汪汪叫"); } } /* 未寫Person類時,DOS視窗運行結果,運行時報錯 C:\Users\86199\Desktop>java ClassLoad_ 1 小狗汪汪叫 =============================================================== C:\Users\86199\Desktop>java ClassLoad_ 2 Exception in thread "main" java.lang.ClassNotFoundException: Person at java.net.URLClassLoader.findClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source) at java.lang.ClassLoader.loadClass(Unknown Source) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Unknown Source) at ClassLoad_.main(ClassLoad_.java:16) */ class Person{ public void hi(){ System.out.println("hi...."); } } /* 寫了Peson類後,DOS視窗運行結果 C:\Users\86199\Desktop>java ClassLoad_ 1 小狗汪汪叫 ================================================= C:\Users\86199\Desktop>java ClassLoad_ 2 hi.... OK */
類載入時機
- 當創建對象時(new),靜態載入;
- 當子類被載入時,父類也載入,靜態載入;
- 調用類中的靜態成員時,靜態載入;
- 通過反射,動態載入;
類載入流程
類載入流程圖
-
類載入過程圖
-
類載入各階段完成的任務
類載入五個階段
-
載入階段(Loading)
JVM在該階段的主要目的是將位元組碼從不同的數據源(可能是class文件、也可能是jar包,甚至網路)轉化為二進位位元組流載入到記憶體中,並生成一個代表該類的java.lang.Class對象;
-
連接階段(Linking)
-
驗證(Verification):
- 目的:是為了確保Class文件的位元組流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全;
- 包括:文件格式驗證(是否以魔數 oxcafebabe 開頭)、元數據驗證、位元組碼驗證和符號引用驗證;
- 可以考慮使用 -Xverify:none 參數來關閉大部分的類驗證措施,縮短虛擬機載入的時間;
-
準備(Preparation):
JVM 會在該階段對靜態變數,分配記憶體並預設初始化(對應數據類型的預設初始值,如0、0L、null、false等)。這些變數所使用的記憶體都將在方法區中進行分配。
package com.hspedu.classload_; /** * @author: 86199 * @date: 2023/6/2 21:19 * @description: 說明類載入夾的連接階段(Linking)-準備(Preparation) */ public class ClassLoad02 { } class A{ //屬性-欄位-成員變數 //分析類載入階段的連接階段(Linking)-準備,屬性是如何處理的 //1. n1是實例屬性,不是靜態變數,因此在準備階段是不會分配記憶體的 //2. n2是靜態屬性,分配記憶體,n2 預設初始化為0,而不是20,20是在連接階段之後的初始化階段進行的 //3. n3是static final 是常量 public int n1 = 1; public static int n2 = 2; public final static int n3 = 3; }
-
解析(Resolution):虛擬機將常量池內的符號引用替換為直接引用的過程。
-
-
初始化(Initialization):
-
到初始化階段,才是真正的開始執行類中定義的Java程式代碼,此階段是執行
()方法的過程; -
()方法是由編譯器按語句在源文件中出現的順序,依次自動收集類中的所有靜態變數的賦值動作和靜態代碼塊中的語句,併進行合併; -
虛擬機會保證一個類的
()方法在多線程環境中被正確地加鎖、同步,如果多個線程同時去初始化一個類,那麼只會有一個線程去執行這個類的 ()方法,其他線程都需要阻塞等待,直到活動線程執行 ()方法完畢; 源碼:
案例演示:
package com.hspedu.classload_; /** * @author: 86199 * @date: 2023/6/2 22:19 * @description: 演示類載入——初始化階段(Initialization) */ public class ClassLoad03_ { static { System.out.println("ClassLoad03_ 靜態代碼塊被執行"); } public static void main(String[] args) throws ClassNotFoundException { //1. Loading:載入B類,並生成B類的Class對象 //2. Linking:num = 0 //3. Initialization: // 依次收集類中所有的靜態變數和靜態代碼塊中的語句入clinit()中,併合並 /* clinit(){ System.out.println("B 靜態代碼塊被執行"); //num = 300; num = 100; } 合併:num = 300, num = 100 --> num = 100 */ // new B();//也會使B類載入,但是B類載入只會執行一遍 // System.out.println(B.num);//直接使用B類的靜態屬性,也會導致B類載入 //看看類載入時,是有同步機制控制的 /* protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { //拿到一個同步鎖,正因為有這個機制,才能保證某個類在記憶體中只有一份Class對象 } } */ Class<?> aClass = Class.forName("B"); } } class B{ static { System.out.println("B 靜態代碼塊被執行"); num = 300; } static int num = 100; public B() {//構造器 System.out.println("B() 構造器被執行..."); } }
-