1. 程式人生 > >類載入機制與反射

類載入機制與反射

一. 類的載入,連線,初始化

  1.1. JVM和類

當呼叫Java命令執行某個Java程式時,該命令將會啟動一個Java虛擬機器程序。不管Java程式多麼複雜,啟動多少個執行緒,它們都處於該Java虛擬機器程序裡,都是使用同一個Java程序記憶體區。

JVM程式終止的方式:

  • 程式執行到最後正常結束
  • 程式執行到使用System.exit()或Runtime.getRuntime().exit()程式碼處結束程式
  • 程式執行過程中遇到未捕獲的異常或錯誤而結束
  • 程式所在平臺強制結束了JVM程序

JVM程序結束,該程序所在記憶體中的狀態將會丟失

  1.2 類的載入

當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過載入、連線、初始化三個步驟來對該類進行初始化。

類的載入時將該類的class檔案讀入記憶體,併為之建立一個java.lang.Class物件,也就是說,當程式使用任何類時,系統都會為之建立一個java.lang.Class物件。

系統中所有的類實際上也是例項,它們都是java.lang.Class的例項

類的載入通過JVM提供的類載入器完成,類載入器時程式執行的基礎,JVM提供的類載入器被稱為系統類載入器。除此之外,開發者可以通過繼承ClassLoader基類來建立自己的類載入器。

通過使用不同的類載入器,可以從不同來源載入類的二進位制資料,通常有如下幾種來源。

  1. 從本地檔案系統載入class檔案,這是前面絕大部分例項程式的類載入方式
  2. 從jar包載入class檔案,這種方式也是很常見的,jdbc程式設計所用的驅動類就放在jar檔案中,JVM可以直接從jar檔案中載入該class檔案。
  3. 通過網路載入class檔案
  4. 把一個Java原始檔動態編譯,並執行載入

類載入器通常無需等到首次使用該類時才載入該類,Java虛擬機器規範允許系統預先載入某些類。

  1.3 類的連線

當類被載入後,系統會為之生成一個對應的Class物件,接著會進入連線階段,連線階段負責把類的二進位制資料合併到JRE中。類的連結可分為如下三個階段。

  1. 驗證:驗證階段用於檢驗被載入的類是否有正確的內部結構,並和其他類協調一致
  2. 準備:類準備階段則負責為類的類變數分配記憶體,並設定預設初始值
  3. 解釋:將類的二進位制資料中的變數進行符號引用替換成直接引用

  1.4 類的初始化

再累舒適化階段,虛擬機器負責對類進行初始化,主要就是對類變數進行初始化。在Java類中對類變數指定初始值有兩種方式:①宣告類變數時指定初始值;②使用靜態初始化塊為類變數指定初始值。

JVM初始化一個類包含如下步驟

  1. 載入並連線該類
  2. 先初始化其直接父類
  3. 依次執行初始化語句

當執行第2步時,系統對直接父類的初始化也遵循1~3,以此類推

  1.5 類初始化時機

當Java程式首次通過下面6種方式使用某個類或介面時,系統會初始化該類或介面

  • 建立類的例項。建立類的例項包括new操作符來建立例項,通過反射來建立例項,通過反射例項化建立例項
  • 呼叫某個類的類方法(靜態方法)
  • 訪問某個類或介面的類變數或為該類變數賦值
  • 使用反射方式來強制來建立某個類或介面的java.lang.Class物件。例如程式碼“Class.forname("Person")”,如果系統還未初始化Person類,則這行程式碼會導致Person類被初始化,並返回person類的java.lang.Class物件
  • 初始化某個類的子類
  • 使用java.exe命令來執行某個主類。當執行某個主類時,程式會初始化該主類

二. 類載入器

  2.1類載入器介紹

類載入器負責將.class檔案載入到記憶體中,併為之生成對應的java.lang.Class物件。

一個載入JVM的類有一個唯一的標識。在Java中,一個類使用全限定類名(包括包名和類名)作為標識;但在JVM中,一個類使用全限定類名和其類載入器作為唯一標識。

當JVM啟動時,會形成由三個類載入器組成的初始類載入器層次結構

  • Bootstrap ClassLoader:跟類載入器
  • Extension ClassLoader:擴充套件類載入器
  • System ClassLoader:系統類載入器

Bootrap ClassLoader被稱為引導(也稱為原始或跟)類載入器,它負責載入Java的核心類。跟類載入器不是java.lang.ClassLoader的子類,而是JVM自身實現的。

Extension ClassLoader負責載入JRE拓展目錄中的JAR包的類,它的父類載入器是跟類載入器

System ClassLoader,它負責在JVM啟動時載入來自Java命令的-classpath選項、java.class,path系統屬性,或CLASSPATH指定的jar包和類歷經。系統可通過ClassLoader的靜態方法或區該系統類載入器。如果沒有特別指定,則使用者自定義的類載入器都已類載入器作為父載入器

  2.2 類載入機制

JVM類載入機制主要有三種

  • 全盤負責。就是當類載入器負責載入某個Class時,該Class所依賴的和所引用的其他Class也將由該類載入器負責載入,除非顯式使用另外一個類載入器來載入
  • 父類委託。所謂父類委託,就是先讓父類載入器試圖載入該Class。只有在父類載入器無法載入該類時才嘗試從自己的類路徑中載入該類
  • 快取機制。快取機制將會保證所有載入過的Class都會被快取,當程式需要使用時,先從快取中搜索該Class,當快取中不存在該Class,系統菜才讀取該類對應的二進位制資料,並將其轉為Class物件,存入快取區中。這就是為什麼修改了Class後,必須重新啟動JVM,程式所做的修改才會生效的原因。

類載入器載入Class大致經過8個步驟

  1. 檢測此Class是否載入過(即快取區中是否有此Class),如果有則直接進入第8步,否者接著第2步
  2. 如果父類載入器(父類      gt+ 載入器,要麼Parent一定是跟類載入器,要麼本身就是跟類載入器)不存在,則調到第4步執行
  3. 請求使用父類載入器載入目標類,如果成功載入調到第8步
  4. 請求使用跟類載入器來載入目標類
  5. 當前類載入器嘗試尋找Class檔案(從與此ClassLoader相關的類路徑中尋找),如果找到則執行第6步,如果找不到執行第7步
  6. 從檔案中載入Class,成功載入調到第8步
  7. 丟擲ClassNotFoundException異常
  8. 返回對應的java.lang.Class物件

其中,第5、6步允許重寫ClassLoader的findClass()方法來實現自己的載入策略,甚至重寫loadClass()方法來實現自己的載入過程。

  2.3 建立並使用自定義的類載入器

JVM除跟類載入器之外的所有類載入器都是ClassLoader子類的例項,開發者可以通過拓展ClassLoader的子類,並重寫該ClassLoader所包含的方法實現自定義的類載入器。ClassLoader有如下兩個關鍵方法。

  • loadClass(String name,boolean resolve):該方法為ClassLoader的入口點,根據指定名稱來載入類,系統就是呼叫ClassLoader的該方法來獲取指定類的class物件
  • findClass(String name):根據指定名稱來查詢類

如果需要是實現自定義的ClassLoader,則可以通過重寫以上兩個方法來實現,通常推薦重寫findClass()方法而不是loadClass()方法。

classLoader()方法的執行步驟:

  1. findLoadedClass():來檢查是否載入類,如果載入直接返回。
  2. 父類載入器上呼叫loadClass()方法。如果父類載入器為null,則使用跟類載入器載入。
  3. 呼叫findClass(String)方法查詢類

從上面看出,重寫findClass()方法可以避免覆蓋預設類載入器的父類委託,緩衝機制兩種策略;如果重寫loadClass()方法,則實現邏輯更為複雜。

ClassLoader的一些方法:

  • Class defineClass(String name,byte[] b,int off,int len):負責將位元組碼分析成執行時資料結構,並檢驗有效性
  • findSystemClass(String name):從本地檔案系統裝入檔案。
  • static getSystemClassLoader():返回系統類載入器
  • getParent():獲取該類載入器的父類載入器
  • resolveClass(Class<?> c):連結指定的類
  • findClassLoader(String name):如果載入器載入了名為name的類,則返回該類對用的Class例項,否則返回null。該方法是類載入快取機制的體現。

下面程式開發了一個自定義的ClassLoader。該classLoader通過重寫findClass()方法來實現自定義的類載入機制。這個ClassLoader可以在載入類之前先編譯該類的原始檔,從而實現執行Java之前先編譯該程式的目標,這樣即可通過該classLoader執行Java原始檔。

複製程式碼
package com.gdut.basic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;

public class CompileClassLoader extends ClassLoader {
private byte[] getBytes(String fileName) {
    File file = new File(fileName);
    Long len = file.length();
    byte[] raw = new byte[(int)len];
    
        FileInputStream fin = new FileInputStream(file);
        //一次讀取class檔案的二進位制資料
        int r = fin.read(raw);
        if(r != len) {
            throw new IOException("無法讀取檔案"+r+"!="+raw);
        
    
    return null;
        }
}
    private boolean compile(String javaFile) throws IOException {
        System.out.println("正在編譯"+javaFile+"...");
        Process p = Runtime.getRuntime().exec("javac"+javaFile);
        try {
            //其他執行緒都等待這執行緒完成
            p.waitFor();
        }catch(InterruptedException ie) {
            System.out.println(ie);
        }
        int ret = p.exitValue();
        return ret == 0;
    }
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class clazz = null;
        String findStub = name.replace(".", "/");
        String javaFileName = findStub+".java";
        String classFileName = findStub+".class";
        File javaFile = new File(javaFileName);
        File classFile = new File(classFileName);
        
        //但指定Java原始檔存在,class檔案不存在,或者Java原始檔的修改時間比class檔案修改的時間更晚時,重新編譯
        if(javaFile.exists() && classFile.exists()
                || javaFile.lastModified() > classFile.lastModified()) {
            try {
            if(!compile(javaFileName)|| !classFile.exists()) {
                throw new ClassNotFoundException("ClassNotFoundExcetion"+javaFileName);
            }
            }catch(IOException ie) {
                ie.printStackTrace();
            }
        }
        if(classFile.exists()) {
            
                byte[] raw = getBytes(classFileName);
                
                clazz = defineClass(name,raw,0,raw.length);
        }
        //如果clazz為null,表明載入失敗,則丟擲異常
        if(clazz == null) {
            throw new ClassNotFoundException(name);
        }
        return clazz;
    }
    
    public static void main(String[] args) throws Exception {
        //如果執行該程式時沒有引數,即沒有目標類
        if (args.length<1) {
            System.out.println("缺少目標類,請按如下格式執行Java原始檔:");
            System.out.println("java CompileClassLoader ClassName");
        }
        
        //第一個引數是需要執行的類
        String progClass = args[0];
        
        //剩下的引數將作為執行目標類時的引數,將這些引數複製到一個新陣列中
        String[] progArgs = new String[args.length - 1];
        System.arraycopy(args, 1,progArgs,0, progArgs.length);
        
        CompileClassLoader ccl = new CompileClassLoader();
        //載入需要執行的類
        Class<?> clazz = ccl.loadClass(progClass);
        //獲取執行時的類的主方法
        Method main = clazz.getMethod("main", (new String[0]).getClass());
        Object argsArray[] = {progArgs};
        main.invoke(null, argsArray);
        
    }
}
複製程式碼

接下來可以提供任意一個簡單的主類,該主類無需編譯就可以使用上面的CompileClassLoader來執行他

複製程式碼
package com.gdut.basic;

public class Hello {

    public static void main(String[] args) {
        for(String arg:args) {
            System.out.println("執行Hello的引數:"+arg);
        }

    }

}
複製程式碼

無需編譯該Hello.java,可以直接執行下面命令來執行該Hello.java程式

java CompileClassLoader hello 瘋狂Java講義

執行結果如下:

CompileClassLoader:正常編譯 Hello.java...
執行hello的引數:瘋狂Java講義

使用自定義的類載入器,可以實現如下功能

  1. 執行程式碼前自動驗證數字簽名
  2. 根據使用者提供的密碼解密程式碼,從而可以實現程式碼混淆器來避免反編譯*.class檔案
  3. 根據應用需求把其他資料以位元組碼的形式載入到應用中。

    2.4 URLClassLoader類

該類時系統類載入器和拓展類載入器的父類(此處的父類,是指類與類之間的的繼承關係)。URLClassLoader功能比較強大,它可以從本地檔案系統獲取二進位制檔案來載入類,也可以從遠端主機獲取二進位制檔案載入類。

該類提供兩個構造器

  • URLClassLoader(URL[] urls):使用預設的父類載入器建立一個ClassLoader物件,該物件將從urls所指定的路徑來查詢並載入類
  • URLClassLoader(URL[] urls,ClassLoader prarent):使用指定的父類載入器建立一個ClassLoader物件,該物件將從urls所指定的路徑來查詢並載入類。

下面程式示範瞭如何從檔案系統中載入MySQL驅動,並使用該驅動獲取資料庫連線。通過這種方式來獲取資料庫連線,無需將MySQL驅動新增到CLASSPATH中。

複製程式碼
package java.gdut;

import java.net.URL;
import java.net.URLClassLoader;
import java.sql.Connection;
import java.sql.Driver;
import java.util.Properties;

public class URLClassLoaderTest {
    private static Connection conn;

    public static Connection getConn(String url,String user,String pass)throws Exception{
        if(conn == null){
            URL[] urls = {new URL("file:mysql-connection-java-5.1.46-bin.jar")};
            URLClassLoader myClassLoader = new URLClassLoader(urls);
            //載入MySQL,並建立例項
            Driver driver = (Driver)myClassLoader.loadClass("com.mysql.jdbc.Driveer").newInstance();

            Properties properties = new Properties();
            properties.setProperty("user",user);
            properties.setProperty("pass",pass);
            //呼叫driver的connect方法來取得資料庫連線
            conn = driver.connect(url,properties);
        }
        return conn;
    }

    public static void main(String[] args) throws Exception {
        System.out.println(getConn("jdbc:mysql://localhost:3306/tb_test","sherman","a123"));
    }
}
複製程式碼

本程式類載入器的載入路徑是當前路徑下的mysql-connection-java-5.1.46-bin.jar檔案,將MySQL驅動複製到該路徑下,這樣保證ClassLoader可以正常載入到驅動類

三. 通過反射檢視類資訊

Java程式中的許多物件在執行時都會出現收到外部傳入的一個物件,該物件編譯時型別是Object,但程式又需要呼叫該物件執行時的方法。

  • 第一種做法是假設編譯時和執行時都知道該物件的的型別的具體資訊,這種情況下,可以先用instanceof()運算子進行判斷,再利用強制型別轉換將其轉換成執行時型別的變數即可
  • 第二種做法是編譯時根本無法知道該物件和類可能屬於那些類,程式只依靠執行時資訊來發現該物件和類的真實資訊,這就必須使用反射

  3.1 獲得class物件

每個類被載入後,系統會為該類生成一個對應的Class物件,通過該Class物件可以訪問到JVM中的這個類。獲得Class物件通常三種方式

  1. 使用Class類的forName(String clazz)靜態方法。字串引數傳入全限定類名(必須新增包名),可能會丟擲ClassNotFoundexception異常。
  2. 呼叫某個類的class屬性來獲取該類的的Class物件。
  3. 呼叫某個物件的getClass()方法,該方法是Object類的一個方法。

對於第一種方式,第二種的優勢:

  • 程式碼更安全。程式在編譯階段就可以檢查需要訪問的Class物件是否存在。
  • 程式效能更好。這的種方式無需呼叫方法,所以效能更好。

  3.2 從Class中獲取資訊

Class類提供了大量的例項方法獲取該Class物件所對應類的詳細資訊

下面4個方法用於獲取Class物件對應類的構造器

  • ConStructor<T> getConStructor(Class<?> parameterTypes):返回Class物件對應類的,帶指定引數列表的public構造器
  • ConStructor<?>[] getConStructor():返回此Class物件對應類的所有public構造器
  • ConStructor<T> getDeclaredConStructor(Class<?>... parameterTypes):返回此Class物件對應類的、帶指定引數列表的構造器,與構造器的訪問許可權無關
  • ConStructor<?>[] getDeclaredConStructor():返回此Class物件對應類的所有構造器,與構造器的訪問許可權無關

下面四個方法獲取Class物件對應類所包含方法。

  • Method getMethod(String name,Class<?> parameterTypes):返回Class物件對應類的,帶指定形參列表的public方法
  • Method[] getMethods():返回Class物件對應類的所有public方法
  • Method getDeclaredMethod(String name,Class<?> parameterTypes):返回Class物件對應類的,帶指定形參列表的方法,與訪問許可權無關
  • Method[] getDeclaredMethods():返回Class物件對應類的所有全部方法,與方法的訪問許可權無關

下面四個方法獲取Class物件對應類所包含的成員變數。

  • Field getField(String name):返回Class物件對應類的,指定名稱的public成員變數
  • Field[] getFIelds():返回Class物件對應類的所有public成員變數
  • Field getDeclaredField(String name):返回Class物件對應類的,指定名稱的成員變數,與成員的訪問許可權無關
  • Field[] getFIelds():返回Class物件對應類的所有成員變數,與成員的訪問許可權無關

如下幾個方法用於訪問Class對應類的上所包含的Annotation.

  • <A extends Annotation>A getAnnotation(Class<A> annotationClass):嘗試獲取該Class物件對應類存在的,指定型別的Annotation;如果該型別的註解不存在,則返回null。
  • <A extends Annotation>A getDeclaredAnnotation(Class<A> annotationClass):Java 8新增方法,嘗試獲取直接修飾該Class物件對應類存在的,指定型別的Annotation;如果該型別的註解不存在,則返回null。
  • Annotation[] getAnnotations():獲取該Class物件對應類存在的所有Annotation
  • Annotation[] getDiclaredAnnotations():獲取直接修飾該Class物件對應類存在的所有Annotation
  • <A extends Annotation>A[] getAnnotationByType(Class<A> annotationClass):由於Java 8的新增了重複註解功能,因此需要使用該方法獲取修飾該Class物件對應類,指定型別的多個Annotation
  • <A extends Annotation>A[] getDeclaredAnnotationByType(Class<A> annotationClass):由於Java 8的新增了重複註解功能,因此需要使用該方法獲取直接修飾該類的,指定型別的多個Annotation

如下方法用於訪問Class對應類的內部類

  • Class<?>[] getDeclaredClass():返回該Class物件對應類裡包含的內部類

如下方法用於訪問Class對應類的所在的外部類

  • Class<?>[] getDeclaringClass():返回該Class物件對應類所在的外部類

如下方法用於訪問Class對應類的所實現的介面

  • Class<?>[] getInterfaces():返回該Class物件對應類的所實現的介面

如下方法用於訪問Class對應類的所繼承的父類

  • Class<? super T> getSuperClass():返回該Class物件對應類的超類的Class物件

如下方法用於訪問Class對應類的修飾符,所在包,類名等基本資訊

  • int getModifiers():返回此類或介面的所有修飾符對應的常量,返回的整數需要Modifier工具類的方法來解碼,才可以獲取真正的修飾符
  • Package getPackage():獲取此類的包
  • String getName():以字串的形式返回該Class物件對應類的類名
  • String getSimpleName():以字串的形式返回該Class物件對應類的簡稱

以下幾個方法來判斷該類是否為介面、列舉、註解型別

  • boolean isAnnotation():返回此Class物件是否表示一個註解型別(有@interface定義)
  • boolean isAnnotationPresent(Class<? extends Annotation>annotationClass):判斷此Class物件是否使用了註解修飾
  • boolean isAnonymousClass():返回此Class物件是否為匿名類
  • boolean isArray():返回此Class物件是否為陣列類
  • boolean isEnum():返回此Class物件是否為列舉類
  • boolean isInterface():返回此Class物件是否為介面
  • boolean isInstance(Object obj):判斷obj是否為該Class物件的例項,該方法可以替代instanceof操作符

以上getMethod()方法和getConStructor()方法中,都需要傳入多個型別為Class<?>的引數,用於獲取指定的方法和構造器。要確定一個方法應該由方法名和形參列表確定。例如下面程式碼獲取clazz對應類的帶一個String引數的info方法:

clazz.getMethods("info",String.class)

  若要獲取clazz對應類的帶一個String引數,一個Integer引數的info方法

clazz.getMethods("info",String.class,Integer.class)

  3.3 Java 8新增加的方法引數反射

Java 8新增了一個Executable抽象基類,該物件代表可執行的類成員,該類派生了Constructor和Method兩個子類。

Executable抽象基類提供了大量方法來獲取修飾該方法或構造器的註解資訊;還提供了is VarArgs()方法用於判斷該方法或構造器是否包含數量可變的形參,以及通過getModifiers()方法獲取該方法或構造器的修飾符。除此之外,還提供如下兩個方法

  • int getParameterCount():獲取該構造器或方法的形參個數
  • Parameter[] getParameters():獲取該構造器或方法的所有形參

Parameter類是Java 8新增的api,提供了大量方法來獲取宣告該方法或引數個數的泛型資訊,還提供瞭如下方法獲取引數資訊

  • getModifiers():獲取修飾該形參的修飾符
  • String getName():獲取形參名
  • Type getParameterizedType():獲取帶泛型的形參型別
  • Class<?> getType():獲取形參型別
  • boolean isNamePresent():該方法返回該類的class檔案中是否包含了方法的形參名資訊
  • boolean isVarArgs():判斷該引數是否為個數可變的形參

需要指出的是,使用javac命令編譯Java原始檔時,預設生成的class檔案並不包含方法的形參名資訊,因此呼叫isNamePresent()將返回false,呼叫getName()也不能得到該引數的形參名。需要編譯時保留形參資訊,則需要該命令指定-parameter選項。

下面示範了Java 8的引數反射功能

複製程式碼
public class MethodParameterTest {
    public static void main(String[] args) throws Exception {
        Class<Test> clazz = Test.class;
        Method replace = clazz.getMethod("replace",String.class,List.class);
        System.out.println("replace方法的引數個數為:"+replace.getParameterCount());

        Parameter[] parameters = replace.getParameters();
        int index = 1;
        for(Parameter parameter:parameters){
            if(!parameter.isNamePresent()){
                System.out.println("-----第"+index+"行的引數資訊-----");
                System.out.println("引數名:"+parameter.getName());
                System.out.println("形參型別:"+parameter.getType());
                System.out.println("泛型型別:"+parameter.getParameterizedType());
            }
        }
    }
}
複製程式碼

  3.4 利用反射生成並操作物件

Class物件可以獲得該類的方法,構造器,成員變數。程式可以通過Method物件來執行對應的方法,通過ConStructor物件呼叫對應的構造器建立例項,能通過Field物件直接訪問並修改物件的成員變數值。

3.4.1 建立物件

通過反射生成物件有兩種方式。

  • 使用Class物件的newInstance()方法來建立該Class物件對應類的例項,這種方式要求該Class物件的對應類有預設構造器。
  • 先使用Class物件獲取指定的Constructor物件,在呼叫Constructor物件的newInstance()方法來建立該Class物件對應類的例項。

3.4.2 呼叫方法

可以通過Class物件的getMethods()方法和getMethod()方法來獲取全部方法和指定方法。

每個Method物件對應一個方法,可以通過它呼叫對應的方法,在Method裡包含一個invoke()方法,該方法的簽名如下。

  • Object invoke(Object obj,Object... args):該方法中的obj是執行該方法的主調,後面的args是執行該方法時傳入該方法的實參。

下面程式是物件池工廠加強版,它允許在配置檔案中增加配置物件的成員變數的值,物件池工廠會讀取為該物件配置的成員變數值,並利用該物件的Setter方法設定成員變數的值。

複製程式碼
package com.gdut.test0516;

import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class ExtendedObjectPoolFactory {
    //定義一個物件池,前面是物件名,後面是實際物件
    private Map<String,Object> objectPool = new HashMap<>();
    private Properties config = new Properties();

    public void init(String fileName)
    {
        try(FileInputStream fis = new FileInputStream(fileName))
        {
            config.load(fis);
        }catch(IOException ex){
            System.out.println("讀取"+fileName+"異常");
        }
    }

   private Object createObject(String clazzName)throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
        Class<?> clazz = Class.forName(clazzName);
        //使用clazz預設構造器建立例項
        return clazz.newInstance();
   }


   public void initPool()throws ClassNotFoundException,
           InstantiationException,IllegalAccessException{
       for (String name:config.stringPropertyNames())
       {
           //沒取出一個key-value對。如果key中不包含百分號(%),即可認為該key用於
           // 控制呼叫物件的setter方法設定值,%前半為物件名字,後半控制setter方法名
       if( !name.contains("%")){
           objectPool.put(name,createObject(config.getProperty(name)));
       }
       }
   }
   public Object getObject(String name){
        return objectPool.get(name);
   }

   public void initProperty()throws NoSuchMethodException,
   IllegalAccessException,InvocationTargetException {
       for (String name:config.stringPropertyNames()) {
           if(name.contains("%")){
               String[] objAndProp = name.split("%");
               Object target = getObject(objAndProp[0]);
               String mtdName = "set"+objAndProp[1].substring(1);
               Class<?> targetClass = target.getClass();
               Method mtd = targetClass.getMethod(mtdName);
               mtd.invoke(target,config.getProperty(name));
           }
       }
   }

    public static void main(String[] args)throws Exception {
        ExtendedObjectPoolFactory epf = new ExtendedObjectPoolFactory();
        epf.init("com/gdut/test0516/extObj.txt");
        epf.initPool();
        epf.initProperty();
        System.out.println(epf.getObject("a"));
    }
}
複製程式碼

  3.4.3 訪問成員變數

通過Class物件的getFields()方法和getField()方法可以獲取該類包含的所有成員變數和指定成員變數。Field提供如下方法讀取或設定成員變數值

  • getXxx(Object obj):獲取Object物件的成員變數值。此處的Xxx對應8種基本型別,如果該成員變數型別時引用型別,則取消get後面的Xxx。
  • setXxx(Object obj,Xxx val):將obj物件的該成員變數設定成val值。此處的Xxx對應8種基本型別,如果該成員變數型別時引用型別,則取消set後面的Xxx。

3.4.4 運算元組

在java.lang.reflect包下還提供了一個Array類,Array物件可以代表所有的陣列。程式可以通過使用該類來建立陣列,運算元組元素等。

Array提供如下方法

  • static Object newInstance(Class<?>ComponentType,int... length):建立一個具有指定的元素型別,指定維度的新陣列
  • static xxx getXxx(Object array,int index):返回陣列array的第index個元素。此處的xxx對應8種基本型別,如果陣列元素是引用型別,則該方法變為get(Object array,int index)。
  • static void setXxx(Object array,int index,Object val):將陣列array的第index個元素設定為val。此處的xxx對應8種基本型別,如果陣列元素是引用型別,則該方法變為set(Object array,int index,Object val)。