歡迎光臨
每天分享高質量文章

反射 — Java 高級開發必須懂的

(給ImportNew加星標,提高Java技能)

 

轉自:博客園,作者:rocomp

鏈接:www.cnblogs.com/rocomp/p/4781987.html

 

理解反射對學習Java框架有很大的幫助,如Spring框架的核心就是使用Java反射實現的,而且對做一些Java底層的操作會很有幫助。 

 

一、Class類的使用

 

1、萬事萬物皆物件,(當然,基本資料型別,靜態成員不是面向物件(屬於類的)),所以我們創建的每一個類也都是物件,即類本身是java.lang.Class類的實體物件,但是這些物件都不需要new出來,因為java.lang.Class類的構造方法是私有的

 

2、任何一個類都是Class類的實體物件,這個實體物件有三種表示方式:(我們新建一個Student類)

 

  1. Class c1 = Student.class;//實際告訴我們任何一個類都有一個隱含的靜態成員變數class(知道類名時用)

     

  2. Class c2 = stu.getClass();//已知該類的物件通過getClass方法(知道物件時用)  

     

  3. Class c3 = Class.forName(“類的全名”);//會有一個ClassNotFoundException異常

 

官網解釋說:c1,c2表示了Student類的類型別()class type),萬事萬物皆物件,類也是物件,是Class類的實體物件,這個物件我們成為該類的類型別(有點亂,但是慢慢捋一下還是能理解的)

 

這裡有一點值得註意,當我們執行System.out.println(c1==c2);陳述句,結果傳回的是true,這是為什麼呢?原因是不管c1還是c2都代表了Student類的類型別,一個類可能是Class類的一個實體物件。

 

我們完全可以通過類的類型別創建該類的物件實體,即通過c1或c2創建Student的實體。

 

Student stu = (Student)c1.newInstance();//前提是必須要有無參的構造方法,因為該陳述句會去呼叫其無參構造方法。該陳述句會丟擲異常。

 

二、動態加載類

 

  1. 編譯時加載類是靜態加載類,

    new 創建物件是靜態加載類,在編譯時刻就需要加載所有可用使用到的類,如果有一個用不了,那麼整個檔案都無法通過編譯

     

  2. 運行時加載類是動態加載類      

    Class c =  Class.forName(“類的全名”),不僅表示了類的型別,還表示了動態加載類,編譯不會報錯,在運行時才會加載,使用接口標準能更方便動態加載類的實現。功能性的類儘量使用動態加載,而不用靜態加載。

 

很多軟體比如QQ,360的在線升級,並不需要重新編譯檔案,只是動態的加載新的東西

      

三、獲取方法信息

 

1、基本的資料型別,void關鍵字都存在類型別

 

Class c1 =int.class;//int的類型別
Class c2 =String.class;//String類的類型別,可以理解為編譯生成的那個String.class位元組碼檔案,
//當然,這並不是官方的說法
Class c3 =double.class;
Class c4 =Double.class;
Class c5 =void.class;

 

2、Class類的基本API操作 

 

/**
* 打印類的信息,包括類的成員函式,成員變數
* @param obj 該物件所屬類的信息
*/
publicstaticvoid printClassMessage(Object obj){
//要獲取類的信息,首先要獲取類的類型別
Class c = obj.getClass();//傳遞的是哪個子類的物件,c就是該子類的類型別
//獲取類的名稱
System.out.println("累的名稱是:"+c.getName());

/*
    * Method類,方法的物件
* 一個成員方法就是一個Method物件
* getMethods()方法獲取的是所有的public的函式,包括父類繼承而來的
* getDeclaredMethods()獲取的是多有該類自己宣告的方法,不問訪問權限
*/
Method[] ms = c.getMethods();//c.getDeclaredMethods();
for(int i =0; i < ms.length; i++){
//得到方法的傳回值型別的類型別
Class retrunType = ms[i].getReturnType();
System.out.print(retrunType.getName()+" ");
//得到方法的名稱
System.out.print(ms[i].getName()+"(");
//獲取的引數型別--->得到的是引數串列的型別的類型別
Class[] paraTypes = ms[i].getParameterTypes();
for(Class class1 : paraTypes){
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
}

 

Class的API中還有很多其他的方法,可以得到interface、Package、Annotation等很多信息,具體使用請參考幫助手冊,本文就不在詳細講解。特別註意的一點是如果你想得到一個類的信息,首先就要獲取該類的類型別。

 

四、獲取成員變數建構式信息  

 

/**
    * 成員變數也是物件,是java.lang.reflect.Field這個類的的物件
* Field類封裝了關於成員變數的操作
* getFields()方法獲取的是所有public的成員變數的信息
* getDeclareFields()方法獲取的是該類自己宣告的成員變數的信息
*/
Field[] fs = c.getDeclaredFields();
for(Field field : fs){
//得到成員變數的型別的類型別
Class fieldType = field.getType();
String typeName = fieldType.getName();
//得到成員變數的名稱
String fieldName = field.getName();
System.out.print(typeName+" "+fieldName);
}


/**
    * 建構式也是物件
* java.lang.Constructor中封裝了建構式的信息
* getConstructor()方法獲取所有的public的建構式
* getDeclaredConstructors得到所有的建構式
*/
Constructor[] cs = c.getDeclaredConstructors();
for(Constructor constructor : cs){
System.out.print(constructor.getName()+"(");
//獲取建構式的引數串列---》得到的是引數雷彪的類型別
Class[] paramTypes = constructor.getParameterTypes();
for(Class class1 : paramTypes){
System.out.print(class1.getName()+",");
}
System.out.println(")");
}

 

五、方法反射的基本操作

 

  1. 如何獲取某個方法

           方法的名稱和方法的引數串列才能唯一決定某個方法

           Method m = c.getDeclaredMethod(“方法名”,可變引數串列(引數型別.class))

     

  2. 方法的反射操作

           m.invoke(物件,引數串列)

           方法如果沒有傳回值,傳回null,如果有傳回值傳回Object型別,然後再強制型別轉換為原函式的傳回值型別

 

六、通過反射瞭解集合泛型的本質

 

ArrayList list1 =newArrayList();
ArrayList list2 =newArrayList();

Class c1 = list1.getClass();
Class c2 = list2.getClass();

System.out.println(c1==c2);//結果為true,為什麼??

 

結果分析:因為反射的操作都是編譯之後的操作,也就是運行時的操作,c1==c2傳回true,說明編譯之後集合的泛型是去泛型化的。

 

那麼我們就可以理解為,Java集合中的泛型,是用於防止錯誤型別元素輸入的,比如在list2中我們add一個int,add(10)就會編譯報錯,那麼這個泛型就可以只在編譯階段有效,通過了編譯階段,泛型就不存在了。可以驗證,我們繞過編譯,用反射動態的在list2中add一個int是可以成功的,只是這時因為list2中儲存了多個不同型別的資料(String型,和int型),就不能用for-each來遍歷了,會丟擲型別轉換錯誤異常ClassCastException

 

 

補充資料:

 

七、關於Java類加載器內容的詳解

 

1、類的加載

 

當程式要使用某個類時,如果該類還未被加載到記憶體中,則系統會通過加載,連接,初始化三步來實現對這個類進行初始化

 

  • 加載:

           就是指將class檔案讀入記憶體,併為之創建一個Class物件,任何類被使用時系統都會建立一個Class物件

     

  • 連接:

            驗證:確保被加載類的正確性

            準備:負責為類的靜態成員分配記憶體,並設置預設初始化值

            解析:將類中的符號取用替換為直接取用

     

  • 初始化:

            區域性變數儲存在棧區:必須手動初始化

            new 的物件儲存在堆區:虛擬機會進行預設初始化,基本資料型別初始化值為0,取用型別初始化值為null

 

2、類加載的時機(只加載一次)

 

以下時機僅表示第一次的時候

 

  1. 創建類的實體的時候
  2. 訪問類的靜態變數的時候
  3. 呼叫類的靜態方法的時候
  4. 使用反射方式來強制創建某個類或接口對應的java.lang.Class物件
  5. 初始化某個類的子類的時候
  6. 直接使用java.exe命令來運行某個主類

 

3、類加載器

 

負責將.class檔案加載到記憶體中,併為之生成對應的Class物件

 

雖然我們在開發過程中不需要關心類加載機制,但是瞭解這個機制我們就能更好的理解程式的運行

 

4、類加載器的組成

 

  1. Bootstrap ClassLoader 根類加載器

          也被稱為引導類加載器,負責Java核心類的加載,比如System類,在JDK中JRE的lib目錄下rt.jar檔案中的類

     

  2. Extension ClassLoader 擴展類加載器

           負責JRE的擴展目錄中jar包的加載,在JDK中JRE的lib目錄下ext目錄

     

  3. System ClassLoader 系統類加載器

          負責在JVM啟動時加載來自java命令的class檔案,以及classpath環境變數所指定的jar包和類路徑,主要是我們開發者自己寫的類

已同步到看一看
赞(0)

分享創造快樂