【java基礎】java反射機制

1、前言

 JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性,方法,泛型類型;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。java

 JAVA反射(放射)機制:「程序運行時,容許改變程序結構或變量類型,這種語言稱爲動態語言」。從這個觀點看,Perl,Python,Ruby是動態語言,C++,Java,C#不是動態語言。可是JAVA有着一個很是突出的動態相關機制:Reflection,用在Java身上指的是咱們能夠於運行時加載、探知、使用編譯期間徹底未知的classes。換句話說,Java程序能夠加載一個運行時才得知名稱的class,獲悉其完整構造(但不包括methods定義),並生成其對象實體、或對其fields設值、或喚起其methods。python

 以上兩句話是借用百度百科的描述,java反射機制使得咱們能夠在運行的時候獲取這個類的幾乎全部的信息,及調用方法,但不能改變類的結構,因此,java反射機制使得java有點像Ruby,Python這種動態語言,但不是動態語言。

2、java類初始化

在java中,一個類要想使用必須通過類的加載,鏈接和初始化這3個操做,正常狀況下這個3個步驟都有jvm來完成。c++

  • 類的加載:類的加載是指定將類的class文件讀入內存,併爲之生成一個java.lang.Class對象,程序使用的每一個類都會在內存中爲之生成一個java.lang.Class對象。程序員

    • 類的加載由類的加載器類完成,jvm向咱們提供了一系列的java類加載器(系統類加載器),其中最經常使用的類加載器有以下四種:
    1. Bootstrap ClassLoader:根類加載器,由jvm自身提供,不是ClassLoader的子類,不是由java實現的,使用本地語言c++實現的,咱們沒法在程序中拿到它。
    2. Extension ClassLoader:擴展類加載器
    3. System ClassLoader(Application ClassLoader):系統類加載器
    4. other ClassLoader:其餘類加載器,如URLClassLoader

    除了系統向咱們提供的類加載器外,咱們還能夠自定義咱們本身的類加載器,自定義類加載器用的很少spring

  • 類的鏈接:當類的加載完成後,系統爲之在內存中生成了Class對象,咱們知道內存中保存的都是二進制數據,全部類的鏈接時將類的二進制數據放到jre(java runtime environment )中,這個過程分爲以下三個階段:
    1. 驗證:對java類進行安全性驗證,包括類是否有正確的內部結構以及和其餘類的協調
    2. 準備:爲類的靜態Field分配內存,並初始化
    3. 解析:將類的二進制數據中的符號替換成直接引用
  • 類的初始化:這個階段,jvm負責對類進行初始化,對Field進行初始化賦值,對Field初始化有以下兩種方式:
    1. 聲明變量的時候爲變量直接賦值
    2. 在靜態代碼塊(static{})中對Field初始化賦值bootstrap

      ps:動態代碼塊({})中的代碼塊在這個階段不會本執行,它是在生成類的實例的時候執行,至關於構造方法中的語句,用的很少api

     用圖畫出類初始化過程以下:數組

    這裏寫圖片描述

 這裏對上面的這個圖作個簡要說明,當要初始化的類有父類時,先初始化其直接父類,初始化其直接父類的時候又會判斷其直接父類是否有父類,而後依次遞歸,直到要初始化的類的全部的父類都被初始化後在初始化最初要初始化的類安全

ps:有初始化語句指的的成員變量直接賦值,及靜態代碼塊等等,當建立某個類的實例,調用了某個類的靜態方法,訪問某個類或接口的Field或者爲之賦值,該類的子類被初始化等等都會致使類初始化ruby

3、java類加載器

 前面說了類的加載器有不少,系統爲咱們提供了四種經常使用的類加載器,下面咱們看看這四種經常使用的類加載器各自的分工及如何協調工做的

  • Bootstrap Class Loader

運行以下代碼:

/**
 * 根類加載器bootstrapClassLoader,不是ClassLoader的子類,是由JVM自身實現的
 * @author lt
 *
 */
public class BootstrapTest {

    public static void main(String[] args) {
        // 獲取根類加載器所加載的全部url數組
        URL[] urLs = sun.misc.Launcher.getBootstrapClassPath().getURLs();
        //輸出根類加載器加載的所有的url
        for(int i = 0;i<urLs.length;i++){
            System.out.println(urLs[i].toExternalForm());
        }
    }

}

控制檯輸出:

file:/D:/Program%20Files/Java/jre7/lib/resources.jar
file:/D:/Program%20Files/Java/jre7/lib/rt.jar
file:/D:/Program%20Files/Java/jre7/lib/sunrsasign.jar
file:/D:/Program%20Files/Java/jre7/lib/jsse.jar
file:/D:/Program%20Files/Java/jre7/lib/jce.jar
file:/D:/Program%20Files/Java/jre7/lib/charsets.jar
file:/D:/Program%20Files/Java/jre7/lib/jfr.jar
file:/D:/Program%20Files/Java/jre7/classes

咱們能夠看到根類加載器所要加載的全部url

  • Extension ClassLoader(擴展類加載器):

     負責加載JRE的擴展目錄(%JAVA_HOME%/jre/lib/ext和有java.ext.dirs系統屬性指定的目錄)中jar包的類

  • Application ClassLoader:

     系統類加載器,負責加載程序員本身寫的類

  • 其餘類加載器:

     加載本身寫的類,如:URLClassLoader既能夠加載本地的類也能夠加載遠程的類

類加載器之間的協調工做:

這裏寫圖片描述

 說明:

  1. jvm加載一個類的時候,先由BootstrapClassLoader加載擴展類加載器,在由擴展類加載加載類或者加載系統類加載器,系統類加載器在加載類或者用戶類加載器
  2. 由於除了根類加載器不是java寫的外,其餘類加載器都是用java寫的,且都是ClassLoader的子類
  3. 加載器的層次結構向咱們展現了四種加載器之間的父子關係,這種父子關係不是指類之間的繼承,而是類加載器實例之間的關係,上圖左圖中向咱們展現了用戶類加載器持有系統類加載器的實例的引用,系統類加載器持有擴展類加載器的實例的引用,擴展類加載中拿不到根類加載器的引用,因此不知道擴展類加載器中是否有根類加載器的引用
自定義類加載器:

 自定義類加載器很是簡單,前面說了java類加載器中除了根類加載器不是ClassLoader外,其餘類加載器都是ClassLoader的子類,ClassLoader中有大量的protected的方法,毫無疑問,咱們能夠經過繼承ClassLoader來擴展類加載器,從而實現咱們本身的類加載器。

  1. loadClass(String name,boolean resolve):ClassLoader的入口, 使用指定的二進制名稱來加載類。
  2. findClass(String name):經過指定的二進制名稱查找類

一般咱們若是重寫loadClass方法時使自定義ClassLoader變得困難,咱們還得本身實現該類的父類委託,緩衝機制兩種策略,而loadClass的執行過程以下:

  1. findLoader(String)來檢查該類是否已經加載,若是加載了則直接返回該類的Class對象,不然返回Null
  2. 調用父類加載器的loadClass()方法,若是父類加載器爲Null,則使用根類加載器
  3. 調用findClass(String)方法查找該類

咱們能夠看到前面兩步是實現父類的一些邏輯,其實這裏兩步實現了父類委託和緩衝機制策略,而咱們只須要從新findClass(String)方法來實現咱們本身的邏輯便可,這樣使自定義類加載器就簡單多了

4、java反射機制

 前面說了類的加載器,下面開始真正學習java反射機制,java反射機制能夠時咱們在運行時刻獲取類的信息,如:類的成員變量類型,值,方法的信息及調用方法,獲取泛型類型,獲取註解等等。

java反射的相關api都在java.lang.reflect包下:學習api最好的方法仍是看官網文檔,由於官方文檔最權威。下面咱們經過一個測試類來學習反射的最經常使用的知識。

// 經過Class的靜態方法forName加載類,該方法會初始化類
Class clazz = Class.forName("Common");
// 經過反射生成該類的實例,調用public的無參構造方法,反射生成類的實例,該類必須得有一個public的無參方法
Object newInstance = clazz.newInstance();
  • 獲取類的構造方法
public static void testConstructor(Class clazz){
        System.out.println("-----------構造方法測試-----------");
        // 獲取因此的構造方法
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for(int i=0;i<declaredConstructors.length;i++){
            int index = i+1; // 由於i從0開始
            String str = "第"+index+"個構造方法權限修飾符爲";
            if(declaredConstructors[i].getModifiers() == 1){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="public";
            }
            if(declaredConstructors[i].getModifiers() == 2){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="private";
            }
            if(declaredConstructors[i].getModifiers() == 4){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="protected";
            }
            str+="名稱爲:"+declaredConstructors[i].getName();
            System.out.println(str);
        }
    }
  • 獲取類方法的相關信息
/**
     * 這個方法經過反射獲取了方法的相關信息,能夠看到咱們獲得了方法
     * 的所有信息
     * 這裏字符串拼接比較多,因爲是測試,因此就沒用StringBuffer拼接
     * @param clazz
     */
    public static void testCommonMethodInfo(Class clazz){
        System.out.println("-----------普通方法測試-----------");
        Method[] methods = clazz.getMethods();
        System.out.println(methods.length);
        for(int i=0;i<methods.length;i++){
            int dexI = i+1;
            String str = "第"+dexI+"個方法形式爲:";
            
            // 獲取方法權限修飾符
            int modifiers = methods[i].getModifiers();
            if(modifiers == 1){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="public";
            }
            if(modifiers == 2){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="private";
            }
            if(modifiers == 4){ // 這裏的常量值能夠查看api文檔,博客下面也會帖子各類修飾符對應的數字
                str+="protected";
            }
            
            String returnType = methods[i].getReturnType().getSimpleName(); // 獲取方法返回類型
            str+=" "+returnType+" "; 
            
            String name = methods[i].getName(); // 獲取方法名稱
            str+= name;
            str+="(";
            
            // 獲取方法參數的類型
            Class[] parameterTypes = methods[i].getParameterTypes();
            for(int j=0;i<parameterTypes.length;i++){
                str+=parameterTypes[j]+" ";
            }
            str+=")";
            System.out.println(str);
        }
    }
  • 調用類的方法
/**
     * 經過反射調用方法
     * @param clazz
     * @throws SecurityException 
     * @throws NoSuchMethodException 
     * @throws InvocationTargetException 
     * @throws IllegalArgumentException 
     * @throws IllegalAccessException 
     */
    public static void testMethodInvoke(Class clazz,Object newInstance) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException{
        System.out.println("-----------測試方法的調用-----------");
        /**
         * 因爲咱們知道類中有這種類型的方法,因此咱們直接指定要獲取的方法類型
         * 若是不知道咱們要調用的 方法的類型,咱們能夠獲取方法的參數類型等等全部信息
         * 而後匹配到咱們須要調用的方法,這裏作個說明,咱們要調用的方法名是知道的
         */
        Method sum = clazz.getMethod("sum",int.class,int.class);
        System.out.println(sum);
        sum.invoke(newInstance, 1,1);
        Method setS = clazz.getMethod("setS",String.class);
        System.out.println(setS);
        setS.invoke(newInstance, "賦值啦");
    }
  • 測試類的屬性Field
/**
     * 測試類的Field
     * @param clazz
     * @throws IllegalAccessException 
     * @throws IllegalArgumentException 
     */
    public static void testField(Class clazz,Object obj) throws IllegalArgumentException{
        System.out.println("-----------測試類的Field-----------");
        // 獲取因此的Field,包括private修飾的,但不能直接獲取和直接改變private修飾的值
        Field[] fields = clazz.getDeclaredFields();
        for(int i=0;i<fields.length;i++){
            int dexI = i+1;
            System.out.print("第"+dexI+"個name:"+fields[i].getName()+"  ");
            Class type = fields[i].getType();
            String typeStr = type.getName();
            try{
                if(typeStr.equals("int")){
                    System.out.println("value:"+fields[i].getInt(obj));
                }else if(typeStr.equals("java.lang.String")){
                    // 字符串形式經過get(Object obj)方法取得,若是該Field的權限爲private,則 獲取值的時候會報java.lang.IllegalAccessException
                    System.out.println("value:"+fields[i].get(obj));
                }
            }catch(IllegalAccessException ex){
                System.out.println("不能獲取private修飾的屬性值");
            }
        }
    }
  • 測試註解
/**
     * 測試註解(類上的註解),屬性方法上的註解分別經過
     * Field對象和Method對象的getAnnotations()方法能夠獲得
     * 和這裏是同樣的
     * @param clazz
     */
    public static void testAnnotation(Class clazz){
        System.out.println("-----------測試註解-----------");
        Annotation[] annotations = clazz.getAnnotations();
        for(int i=0;i<annotations.length;i++){
            System.out.println(annotations[i]);
        }
    }
  • 測試泛型
/**
     * 泛型測試
     * @throws NoSuchFieldException
     */
    private static void genericTest() throws NoSuchFieldException {
        System.out.println("-----------獲泛型測試-----------");
        Class<ReflectTest> clazz  = ReflectTest.class;
        Field f = clazz.getDeclaredField("map");
        // 直接使用getType()只對普通類型有效,並不能獲得有泛型的類型
        
        Class<?> type = f.getType(); 
        // 下面的代碼能夠看到只輸出了java.util.Map
        System.out.println(" map 的類型爲:"+type);
        // 獲取Field實例f的泛型類型
        Type genericType = f.getGenericType();
        // 若是genericType是ParameterizedType對象
        if(genericType instanceof ParameterizedType){
            ParameterizedType parameterizedType = (ParameterizedType) genericType;
            // 獲取原始類型
            Type rawType = parameterizedType.getRawType();
            System.out.println("原始類型是:"+rawType);
            // 取得泛型類型的泛型參數
            Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
            for(int i=0;i<actualTypeArguments.length;i++){
                System.out.println("第"+i+"個泛型類型爲:"+actualTypeArguments[i]);
            }
        }else{
            System.out.println("泛型類型獲取出錯");
        }
    }
  • 獲取泛型的實際類型
/**
     * 獲取泛型T的實際類型
     */
    public static void getTType(){
        System.out.println("-----------獲取泛型T的實際類型-----------");
        Dog dog = new DogImpl();
        // 注意,下面這種寫法不能獲取到T的實際類型,由於範式要在編譯的時候就指定類型,在運行時候指定類型是獲取不到真實類型的
//      Dog<Cat> dog = new Dog<Cat>();
    }
修飾符常量對應的值:

這裏寫圖片描述

 因爲註釋說了很清楚了,因此這裏就不過多介紹了,以上這些方法都是參考了api提供的一些方法本身寫的一些測試,其實都只是一個簡單方法的使用,但這些方法是反射中最基本最經常使用的方法,api方法不少,因此咱們學習api的時候最好時刻查詢文檔,查看文檔是個好習慣。

 總結,java反射機制是java的一個很是重要的一個知識點,其實向spring,struts等等一些知名的框架沒有一個沒有使用反射,因此學好java反射是提高咱們技術必不可少的,咱們應該掌握java反射機制。這篇文章與其說是java反射機制的講解,實際上是我本身學習java反射的學習總結,由於學習java也很久了,回顧總結一下所寫的知識點,因此文章內容有說錯的部分,歡迎指出。

測試代碼:http://download.csdn.net/detail/ydxlt/9310157

相關文章
相關標籤/搜索