首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 程式的類加載與反射機制深度解析

     編輯:Java 相關知識

Java 中類檔案加載是動態的。JVM 指令封裝在 .class 檔案中,加載過程是動態的——即用到時才加載,不用則不加載。「用到」包含兩種方式:一是 new 一個物件時(注意涉及多態時,編譯器會優化,提前加載涉及多態的類,詳見範例 1);二是當一個類的靜態代碼被調用時。

範例 1:多態與類加載時機

// Zoo.java
abstract class Animal {
    Animal() { System.out.println("Animal constructor"); }
}
class Tiger extends Animal {
    Tiger() { System.out.println("Tig constructor"); }
}
class Dog extends Animal {
    Dog() { System.out.println("Dog Constructor"); }
}
public class Zoo {
    private Animal am; // Example 1.1
    // private Dog am; // Example 1.2
    private Tiger tiger;
    Zoo() {
        tiger = new Tiger();
        am = new Dog();
    }
    public static void main(String[] args) {
        System.out.println("new Zoo before");
        Zoo z = new Zoo();
        System.out.println("new Zoo after");
    }
}

註釋掉 Example 1.1,運行 Example 1.2,結果顯示:當將子類物件賦值給父類引用時,編譯器優化,加載器在 new 子類物件前已加載父類與子類(Example 1.1 結果);不存在多態時,只有在 new Dog() 時才加載 Dog 及父類。無論何種方式,在 new 之前,類確已加載進記憶體。

Java 提供的兩種動態機制

  • 隱式機制:new 物件與調用類靜態方法時,隱式機制在工作。
  • 顯式機制:兩種策略——java.lang.Class.forName(String) 與 java.lang.ClassLoader.loadClass()。

策略一:使用 Class.forName

forName 有兩種形式:

public static Class forName(String className) throws ClassNotFoundException
public static Class forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException

第二個方法關鍵在於第二個參數 boolean initialize:若設為 false,加載完類後不會執行靜態代碼與靜態初始化,只有 new 物件時才初始化。第三個參數指定類加載器。

查看 java.lang.Class 源碼,兩方法最終調用私有 native 方法 forName0:

private static native Class forName0(String name, boolean init, ClassLoader loader) throws ClassNotFoundException;

調用 Class.forName(name) 實際內部調用:

forName0(name, true, ClassLoader.getCallerClassLoader());

調用 Class.forName(name, initialize, loader) 實際內部調用:

forName0(name, initialize, loader);

範例 2.1:第二個參數為 false 的情況:

// Zoo.java
abstract class Animal {
    static { System.out.println("Animal static code block"); }
    Animal() { System.out.println("Animal constructor"); }
}
class Tiger extends Animal { Tiger() { System.out.println("Tig constructor"); } }
class Dog extends Animal { Dog() { System.out.println("Dog Constructor"); } }
public class Zoo {
    public static void main(String[] args) throws Exception {
        System.out.println("new Zoo before");
        Zoo z = new Zoo();
        Class c = Class.forName("Dog", false, z.getClass().getClassLoader());
        System.out.println("initialize before");
        Animal dog = (Animal)c.newInstance();
        System.out.println("new Zoo after");
    }
}

類加載完成後並未立即執行靜態初始化代碼,而是到實例化時才進行靜態初始化。有時說靜態代碼在類第一次加載時執行且僅一次,這針對 new 物件、第一次訪問靜態代碼及第二參數為 true 而言;對動態加載,若 forName 第二參數設 false,則在實例化時才執行靜態初始化。預設第二參數為 true。

策略二:使用 ClassLoader

範例 2.2:

// Zoo.java
abstract class Animal {
    static { System.out.println("Animal static code block"); }
    Animal() { System.out.println("Animal constructor"); }
}
class Tiger extends Animal { Tiger() { System.out.println("Tig constructor"); } }
class Dog extends Animal { Dog() { System.out.println("Dog Constructor"); } }
public class Zoo {
    public static void main(String[] args) throws Exception {
        Class c = Zoo.class;
        ClassLoader loader = c.getClassLoader();
        System.out.println("loader before");
        Class dog = loader.loadClass("Dog");
        System.out.println("instance before");
        Animal an = (Animal)dog.newInstance();
    }
}

loader 完成後並未立即執行靜態代碼,只有 newInstance() 時才執行靜態初始化,這與 forName 第二參數指定為 false 完全一致。每當編譯單元完成就得到一個 .class 檔案,其中包含該類的 Class 物件,JVM 利用此 Class 物件動態加載類。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved