1. 程式人生 > >Java 反射(Reflection)

Java 反射(Reflection)

學習Spring等框架時,接觸到反射這一機制,查閱資料發現Java的反射機制對於各大主流Java框架有著舉足輕重的地位。在此特地記錄自己對於reflect相關知識的些許理解。如有錯誤之處,還請指正。

1、簡單理解什麼是反射

反射機制可以讓程式在執行時 載入、探知、使用一個在編譯期完全未知的類,通過反射可以讓我們在只知道類名的情況下,獲取一個Java類的內部成員變數、成員方法。並對其成員進行操作(方法呼叫等)。換句話說,程式可以載入一個執行時才得知名稱的Java類,獲悉其完整構造(但不包括方法(method)的完整定義),並生成其物件例項、或對其fields(屬性)設值、或呼叫其methods。

Java能夠實現反射機制,ClassLoader、Class、Method、Field 、Constructor等 這幾個類是至關重要的。

2、ClassLoader類

類載入器(ClassLoader的實現)是負責載入類的物件。ClassLoader 類是一個抽象類。如果給定類的二進位制名稱,那麼類載入器會試圖查詢或生成構成類定義的資料。一般策略是將名稱轉換為某個檔名,然後從檔案系統讀取該名稱的“.calss檔案”。
當一個Java類被載入,或當載入器(classloader)的defineClass()被JVM呼叫,JVM 便例項化一個Class 物件。這個Class物件是被載入類的位元組碼物件(記憶體中一個類的位元組碼只有一份,這個Class物件也就只有一個)。
但是,陣列的 Class 物件並不是由類載入器建立的,而是由 JRE(Java 執行時)根據需要自動建立。
每個 Class 物件都包含了對於定義它的 ClassLoader 的引用。通過呼叫Class 物件的 getClassLoader()方法,可以獲取該ClassLoader物件。

3、Class 類

Class類是一個特殊的類,他繼承自Object,並且沒有public的建構函式 。
Class 類的物件(位元組碼物件)表示正在執行的 Java 應用程式中的類和介面。(列舉是一種類,註解是一種介面)。不止如此,JVM還預先提供了9種Class物件,分別是:
byte、short、int、long、float、double、boolean、char、void (8種基本資料型別和關鍵字void)的Class物件。

3.1、獲取Class物件

3.1.1、獲取類的Class物件,有三種方式:

Student stu = new Student();
 Class c1 = Student.class
; Class c2 = stu.getClass(); Class c3 = Class.forName("com.company.demo.Student"); //注意:同一個類 在JVM中只有一份位元組碼,所以c1、c2、c3 指向同一個Class物件
  • c1方式: 每個類都有一個class屬性,該屬性包含了指向該類Class物件的引用
  • c2方式:通過類的例項,來獲取類的Class物件
  • c3方式:Class.forName(String className)方法,引數類名必須是完整的類名(包名+類名)。該方式下:當指定類的位元組碼已經載入到JVM中時,獲取到位元組碼,並返回該類的Class物件;否則 載入指定的類,並返回位元組碼物件。

3.1.2、對於基本資料型別、void 的 Class物件的獲取方式:

byte.class    short.class    int.class    long.class 
float.class    double.class    boolean.class    char.class   
void.class

此外,在8大基本資料型別對應的包裝類中有一個 TYPE 常量,它包含了該包裝類所對應基本資料型別的Class物件的引用。以Boolean類為例:

  public static final Class<Boolean> TYPE = Class.getPrimitiveClass("boolean");

所以 : Boolean.TYPE == boolean.class
但是 Boolean.class != boolean.class 因為兩者不是同一資料型別,位元組碼不同。

3.2、Class 常用方法:

3.2.1
public String toString() {
return (isInterface() ? “interface ” : (isPrimitive() ? “” : “class “))
+ getName();
}

3.2.2
public T newInstance()
throws InstantiationException, IllegalAccessException
建立此 Class 物件所表示的類的一個新例項。如同用一個帶有一個空引數列表的 new 表示式例項化該類。如果該類尚未初始化,則初始化這個類。

3.2.3
public ClassLoader getClassLoader()
返回該Class物件表示類(或介面)的類載入器。若該Class物件表示基本資料型別或void,則返回null

3.2.4

public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

public Method[] getMethods() throws SecurityException

public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

public Method[] getDeclaredMethods() throws SecurityException 

3.2.5

public Field getField(String name)throws NoSuchFieldException, curityException

public Field[] getFields() throws SecurityException

public Field getDeclaredField(String name)
    throws NoSuchFieldException, SecurityException

public Field[] getDeclaredFields() throws SecurityException

4、初次接觸Method 類

Method物件表示著類(或介面)的方法, 通過Method物件,可以讓我們在知道類名,但不瞭解類的具體定義的情況下呼叫類的方法。

 //返回Person類的Class物件
 Class c1 = Class.forName("com.company.demo.Person");
 //新建Person類的例項
 Person p = (Person) c1.newInstance();
 //返回Person類中sayParam方法對應的Method物件
 //(第一個引數為方法名,第二個引數為sayParam方法的形參型別對應的Class物件)
 Method method = c1.getMethod("sayParam",String.class);
 //第一個引數為 method物件代表的方法所屬的物件,如果是靜態方法,該引數可為null
 //第二個引數為 method物件代表方法(sayParam(String param))所需的引數
 method.invoke(p,"nothing is impossible");

Method 的invoke方法會喚起 指定的例項(如Person例項 p)去呼叫這個例項自身中相應的方法(Method物件表示的方法;如sayParam(String param))。

5、陣列的反射

陣列型別的 Class 物件並不是由類載入器建立的,而是由 Java 執行時根據需要自動建立。陣列類的類載入器由 Class.getClassLoader() 返回,該載入器與其元素型別的類載入器是相同的;如果該元素型別是基本型別,則該陣列類沒有類載入器。

獲取陣列的Class物件有兩種方式:
1、陣列物件.getClass();
2、陣列型別.class;

注意:具有相同維數、相同型別的陣列 共享一份位元組碼,共享一個Class物件。