Java中類加載和反射技術實例


       我們知道一個對象在運行時有兩種類型,一個是編譯類型,一個是運行時類型。在程序運行時,往往是需要發現類和對象的真實的信息的。那么如何獲的這種信息呢?

其一,如果我們在編譯和運行時都知道類型的具體信息,這時是可以手動將一個對象轉換為運行時的類型。

其二,如果我們在編譯時無法預知對象和類到底是屬於哪些類,那么程序只有依靠運行時的信息來發現對象和類的真實的信息了,這時就必須要用到反射技術。

在談具體的反射技術之前,我想先回顧下,有關類的加載的一些基本的性質和原理,以方便我們更好地理解,反射的作用和特點。而實際上,一個類如果可以被執行,那么對於JVM來說,它的執行流程為:類的加載、連接、初始化。通過這種方式,才可以獲取到一個類的類對象,即java.lang.Class對象,並在此基礎上獲取到該類的成員變量,方法和構造器等內在的東東。

   那么,什么是類的加載呢?類的加載就是將類的class文件讀入內存,並為之創建一個java.lang.Class對象,也就是說當程序使用任何類時,系統都會為之建立一個java.lang.Class對象。

同時,這個java.lang.Class對象又有什么特點呢?

1、Class是一個類,一個描述類的類(也就是描述類本身),封裝了描述方法的Method,描述字段的Filed,描述構造器的Constructor等屬性
2、對象照鏡子后(反射)可以得到的信息:某個類的數據成員名、方法和構造器、某個類到底實現了哪些接口。
3、對於每個類而言,JRE 都為其保留一個不變的 Class 類型的對象。一個 Class 對象包含了特定某個類的有關信息。
4、Class 對象只能由系統建立對象
5、一個類在 JVM 中只會有一個Class實例

同時,我們了解下一個基本的類加載器的結構:

類的連接:連接階段負責把類的二進制數據合並到JRE中,分為三個步驟:

第一,驗證,檢驗被加載的類是否有正確的內部結構;第二,准備,負責為類的類變量分配內存,並設置默認初始值。第三,解析將類的二進制數據中的符號引用替換成直接引用。

 

類的初始化:主要對類變量進行初始化。

 

這樣,我們清楚地認識到了一個類的生命周期變化,那么這些也為我們的反射機制帶來了鋪墊,通過反射要獲取一個類的class對象,有三種方式:

       (1)使用Class類的forName(String clazzName)靜態方法 

       (2)調用某個類的class屬性獲取該類對應的Class對象

       (3)調用某個類的getClass()方法

       通過這三種手段就可以獲取到了一個類的Class對象,這樣就可以動態獲取該對象的實際的信息了。下面通過一個具體的例子說明下,如果通過反射機制,創建一個類的對象,如何通過獲取的對象再進一步獲取該對象的屬性和方法,以及動態為該對象注入新的參數值。

 測試實體類:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description
 * @date 2017/10/19
 */
public class Person {
    public String name;
    private int age;

    public Person(){
        System.out.println("無參構造器");
    }
    public Person(String name,int age){
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    private void privateMethod(){
        System.out.println("這是一個私有的方法!");
    }

    public void setName(String name,int age){
        System.out.println("getName is:"+name);
        System.out.println("getAge is:"+age);
    }

}

通過反射獲取類:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通過反射獲取類測試
 * @date 2017/10/19
 */
public class GetClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        //Class是一個描述類的類、對每個類而言只有一個不變的Class與其對應、JRE中只有唯一一個
        Class clazz = null;

        //1 直接通過類名.class方式得到
        clazz = Person.class;
        //輸出:reflection.Person
        System.out.println("直接通過類名.class得到:"+clazz);

        //2 通過對象的getClass()方法獲取
        Object obj = new Person();
        clazz = obj.getClass();
        //輸出:reflection.Person
        System.out.println("通過getClass(): " + clazz);

        //3 通過全類名獲取,用的比較多,但可能拋出ClassNotFoundException異常
        clazz = Class.forName("reflection.Person");
        //輸出:reflection.Person
        System.out.println("通過全類名獲取: " + clazz);
    }
}

獲取類的實例:

package reflection;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通過反射獲取類的實例測試
 * @date 2017/10/19
 */
public class GetInstanceTest {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class clazz = Class.forName("reflection.Person");
        //使用Class類的newInstance()方法創建類的一個對象
        //實際調用的類的那個 無參數的構造器
        //一般的,一個類若聲明了帶參數的構造器,也要聲明一個無參數的構造器
        Object obj = clazz.newInstance();
        System.out.println(obj.toString());
    }
}

獲取類所定義的方法:

package reflection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通過反射獲取類所定義的方法的測試
 * @date 2017/10/19
 */
public class GetMethodTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        //獲取clazz
        Class clazz = Class.forName("reflection.Person");

        //1、得到clazz 對應的類中有哪些方法,不能獲取private方法
        Method[] methods = clazz.getMethods();
        System.out.println("getMethods:");
//遍歷方法集合 獲取對應的方法名稱
for (Method method : methods) { System.out.print(method.getName() + ", "); } System.out.println(); //2、獲取所有的方法(包括private方法) Method[] methods2 = clazz.getDeclaredMethods(); System.out.println("getDeclaredMethods:"); for (Method method : methods2) { System.out.print(method.getName() + ", "); } System.out.println(); //3、獲取指定的方法 Method method = clazz.getDeclaredMethod("setName", String.class); System.out.println("method : " + method); Method method2 = clazz.getDeclaredMethod("setName", String.class, int.class);//第一個參數是方法名,后面的是方法里的參數 System.out.println("method2: " + method2); //4、執行方法 Object obj = clazz.newInstance();
//此處注意下該invoke方法,參數為:執行該方法的對象,為該方法傳入的參數列表 method2.invoke(obj,
"jaycee", 23); } }

獲取Fileds:

package reflection;

import java.lang.reflect.Field;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 通過反射獲取field測試
 * @date 2017/10/19
 */
public class GetFieldTest {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class clazz = Class.forName("reflection.Person");

        //1、獲取字段
        //1.1 獲取Field的數組,私有字段也能獲取
        Field[] fields = clazz.getDeclaredFields();
        for (Field field: fields) {
            System.out.print(field.getName() + ", ");
        }

        //1.2 獲取指定名字的Field
        Field field = clazz.getDeclaredField("name");
        System.out.println("\n獲取指定Field名=: " + field.getName());

        Person person = new Person("jaycee", 23);
        //2、獲取指定對象的Field的值
        Object val = field.get(person);
        System.out.println("獲取指定對象字段'name'的Field的值=:" + val);

        //3、設置指定對象的Field的值
        field.set(person, "admin");
        System.out.println("設置指定對象字段'name'的Field的值=: " + person.name);

        //4、若該字段是私有的,需要調用setAccessible(true)方法
        Field field2 = clazz.getDeclaredField("age");
        field2.setAccessible(true);
        System.out.println("獲取指定私有字段名=: " + field2.getName());
    }
}

獲取父類定義的信息:

申明父類:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 父類
 * @date 2017/10/19
 */
public class Father {
    /**
     * 父類無參構造函數
     */
    public Father(){

    }
    /**
     * 父類私有方法1
     */
    private String method(){
        return "Father's private method";
    }
}

申明子類:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 兒子類
 * @date 2017/10/19
 */
public class Son extends Father{
    /**
     * @author jiaqing.xu@hand-china.com
     * @date 2017/10/19 10:19
     * @param
     * @return
     * @description 兒子私有方法
     */
    private void method1(Integer age){
        System.out.println("son's age=:" +age);
    }
}

獲取父類中定義的方法:

package reflection.invoke;

/**
 * @author jiaqing.xu@hand-china.com
 * @version 1.0
 * @name
 * @description 獲取父類中定義的私有的方法
 * @date 2017/10/19
 */
public class GetSuperClassTest {
    public static void main(String[] args) throws ClassNotFoundException {
        String className = "reflection.invoke.Son";
        Class clazz = Class.forName(className);
        //得到父類的Class
        Class superClazz = clazz.getSuperclass();
        //輸出:class reflection.invoke.Father
        System.out.println(superClazz);
    }
}

 總結下反射的優點和缺點:

1 優點:
2 (1)提高程序的靈活性和拓展性,能夠在運行時動態地獲取類的實例。
3 (2)和java的動態編譯相結合,可以提高更強大的功能。
4 (3)提前無需硬編碼,便可以通過類名獲取對應類的實例,進而操作該實例。
5 
6 缺點:
7 (1)性能較低,反射是一種接入式的操作需要找到對應的字段和方法,比起直接的代碼賦值,要慢的多。
8 (2)使用反射應該在一個相對安全的環境下進行。
9 (3)使用時會破壞類的封裝性,破壞OOP的整體設計思想。

 


免責聲明!

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



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