java基礎——反射

weixin_34087301發表於2016-12-23

JAVA反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制。

Class類和java.lang.reflect類庫一起對反射機制進行了支援,該類庫包含了Field、Method以及Constructor類。下面就從這兩大方面對反射進行介紹:

1. Class類

類作為程式的一部分,每個類都擁有一個Class物件。每當編寫並編譯一個新類,就會產生一個Class物件(儲存在一個同名的.class檔案中)。為了生成這個類的物件,jvm使用了類載入器系統。
所有的類都是在第一次使用時,動態載入到jvm中的。類載入器首先檢查這個類的Class物件是否已經載入,未載入就會查詢.class檔案(本地或網路獲取)。

1.1 Class類獲取

以下三種方式均可以獲取Class類:

return reportInfo;
Integer num = new Integer(123);
Class c1 = num.getClass();
Class c2 = Integer.class;
Class c3 = Class.forName("java.lang.Integer");
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class cl4 = loader.loadClass("java.lang.Integer");

注意點

  • .class方式不會引起類的初始化,而Class.forName會引起對應類進行初始化。
  • 基本的 Java 型別(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也都對應一個 Class 物件。
  • 每個陣列屬於被對映為 Class 物件的一個類,所有具有相同元素型別和維數的陣列都共享該 Class 物件。

1.2 Class提供常用方法

  1. getClassLoader() :返回該類的類載入器
  2. isArray() :判斷是否是陣列
  3. getName():以 String 的形式返回此 Class 物件所表示的實體(類、介面、陣列類、基本型別或 void)名稱
  4. newInstance():可以建立該類的示例
  5. 同時提供了獲取Constructor、Method和Field的方法,後續會詳細說明

1.3 Class使用技巧

  • forName和newInstance結合起來使用,可以根據儲存在字串中的類名建立物件,例如:Object obj = Class.forName("xxxx").newInstance();
  • 虛擬機器為每種型別管理一個獨一無二的Class物件。因此可以使用==操作符來比較類物件,例如:if(e.getClass() == Employee.class)

2. reflect包

reflect包中有三個類,Field,Method,Constructor,分別去描述類的域,方法,構造器。
通過Class類可以分別獲取上述三個類的具體例項,下面進行分別講述:

2.1 Field類

表示類的成員變數,其中一個成員變數對應一個Field物件。Class物件獲取Field方法如下:

  • getFields():獲得類的public型別的屬性
  • getDeclaredFields():獲得類的所有屬性
  • getField(String name):獲取指定名稱public型別屬性
  • getDeclaredFields(String name):獲取指定名稱屬性

通過上述4種方法,我們可以獲取指定的Field物件。通過Field物件我們可以實現以下常見功能:

  1. String getName():獲取欄位名
  2. Class<?> getType() 和 Type getGenericType() :獲取型別和泛型
  3. int getModifiers():獲取修飾
  4. Object get(Object obj):獲取指定物件該欄位對應值
  5. void set(Object obj, Object value):給指定物件的該段賦值

這裡演示一下如何修改private的屬性值:

3221368-267f5a53e071429b.png
修改private屬性值

這裡需要注意的是在賦值給private屬性之前需要呼叫field.setAccessible(true)方法關閉對private屬性訪問檢查。

2.2 Method類

表示類的成員方法,其中一個成員方法對應一個Method物件。Class物件獲取Method方法與Filed類似如下:

  • getMethods():獲得類的public型別的方法
  • getDeclaredMethods():獲得類的所有方法
  • getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name引數指定方法的名字,parameterTypes 引數指定方法的引數型別,同時該方法為public的
  • getDeclaredMethod(String name, Class<?>... parameterTypes):同上只是範圍擴大到所有方法

同樣獲取Method物件以後,我們可以實現以下常見功能:

  1. Class<?>[] getParameterTypes():獲取該方法的所有引數型別
  2. Class<?> getReturnType():獲取該方法返回值
  3. <T extends Annotation> T getAnnotation(Class<T> annotationClass):獲取方法註解
  4. Object invoke(Object obj, Object... args)執行該方法

同樣這裡演示如何執行一個private方法:

3221368-528ac52c2e241759.png
執行private方法

同樣在執行private方法之前需要執行method.setAccessible(true),關閉對private方法訪問檢查。

2.3 Constructor類

表示類的構造方法,其中一個構造方法對應一個Constructor物件。Class獲取Constructor的方法如下:

  • getConstructors():獲得類的public型別的構造方法
  • getDeclaredConstructors():獲取所有構造方法
  • getConstructor(Class<?>... parameterTypes):獲得類的特定public構造方法,parameterTypes 引數指定構造方法的引數型別。
  • getDeclaredConstructor(Class<?>... parameterTypes):同上範圍擴大到所有構造方法
    同樣獲取Constructor物件以後,我們可以實現以下常見功能:
  1. Class<?>[] getParameterTypes():構造方法引數列表
  2. T newInstance(Object ... initargs):例項化對應類

接下來示例展示如何利用Constructor物件例項化對應類

3221368-4fd493fe5197d207.png
Constructor例項化物件

2.4 反射與泛型

常見兩種泛型使用場景:

  • 宣告一個需要被引數化(parameterizable)的類/介面,例如:class MyClass<T>
  • 使用一個引數化類,例如:List<String> arrays;
2.4.1Type介面

在此之前我們需要介紹一下Type介面,Java程式語言中所有型別公共父介面,這裡所說的所有型別包括:
原始型別 (raw types)對應Class,引數化型別 (parameterizedtypes)對應ParameterizedType, 陣列型別 (array types)對應GenericArrayType,型別變數 (type variables)對應TypeVariable,基本資料型別(primitivetypes)仍然對應Class。

Class類實現了該介面,同時該介面的直接子介面包括以下4個:

  • ParameterizedType: 表示一種引數化的型別,比如Collection<String>
  • GenericArrayType: 表示一種元素型別引數化型別或者型別變數陣列型別
  • TypeVariable: 是各種型別變數公共父介面
  • WildcardType: 代表一種萬用字元型別表示式

配合後面的反射內容重點介紹一下java.lang.reflect.ParameterizedType介面

  • 含義:表示引數化型別比如:Map<String, Date>這種引數化型別
  • Type[] getActualTypeArguments():獲取引數化型別<>中的實際型別(注意對於多次巢狀,該方法只返回脫去最外層的<>以後剩下內容返回)
  • 關於返回值是陣列因為存在Map<String, Date>返回不止一種型別
  • 為什麼返回父介面,因為返回值多型性,對於ArrayList<ArrayList<Integer>>返回的是ArrayList<Integer>ParameterizedType型別的,而對於ArrayList<E>返回的是E是TypeVariable型別,而對於 對於ArrayList<E[]>返回的是E[]則對應的是GenericArrayType型別。
  • Type getRawType():獲取承載該泛型資訊的物件,該型別表示<>前面的型別比如Map<String,String>,則返回Map
2.4.2引數化型別反射獲取

講述完Type相關內容以後,我們來看下Field、Method和Constructor相關類如何獲取泛型引數或泛型返回值。這裡我們以Method為例說明:

3221368-c082ec28cf108476.png
方法返回值引數化型別獲取

在這個示例中我們重點關注一下method.getGenericReturnType()方法,返回返回值Type,檢視jdk原始碼:

3221368-bc4df773ceae5a34.png
getGenericReturnType
3221368-f607fbe3ea8f573f.png
getReturnType

觀察程式碼可以發現在獲取泛型返回是首先判斷getGenericSignature()是否為空該函式是native的沒找到相關資料,從名稱推斷是標識是否是泛型,在Field相關原始碼中也有出現。如果返回不問空則返回ParameterizedType型別,否則呼叫getReturnType返回Class<?>型別。
檢視Field、Constructor相關程式碼可以看出相似使用方法:

  • Field類中屬性型別 Class<?> getType()和Type getGenericType()
  • Method類中方法返回值型別Class<?> getReturnType() 和Type getGenericReturnType()
  • Method類中方法引數型別Class<?>[] getParameterTypes()和Type[] getGenericParameterTypes()
  • Constructor類中方法引數和Method中方法引數完全一致

最後,如果需要解析Field、Method、Constructor中泛型相關型別,使用步驟和給出示例類似:

  1. 呼叫相關Genric對應的方法
  2. 判斷返回值instanceof ParameterizedType
  3. 步驟為true強制轉換為ParameterizedType型別
  4. 後續呼叫getActualTypeArguments等方法獲取真實引數

參考文件

http://lavasoft.blog.51cto.com/62575/15433/
http://developer.51cto.com/art/201103/250028.htm
http://blog.csdn.net/benjaminzhang666/article/details/9838937

相關文章