Java 反射詳解

Java 反射是一個比較重要的知識點,你會在不少地方見到反射。它提供了 Java 語言在運行期間加載、探知和使用編譯期間徹底未知的類的能力。這種能力在框架的編寫中很是常見,例如動態代理中、類掃描解析中。html

反射的定義與做用

反射機制:即 Java 語言在運行時有一種自觀的能力,可以瞭解自身的狀況併爲下一步的動做作準備。反應出來就是,在運行時,對於一個類,咱們可以知道該類有哪些方法和屬性。對於一個對象,咱們可以調用其任意的一個方法。這是一種動態獲取類的信息以及動態調用對象方法的能力。java

實現反射的基礎

Java 提供反射機制,依賴於 Class 類和 java.lang.reflect 類庫。其主要的類以下:api

  1. Class:表示類或者接口
  2. Field:表示類中的成員變量
  3. Method:表示類中的方法
  4. Constructor:表示類的構造方法
  5. Array:該類提供了動態建立數組和訪問數組元素的靜態方法

Class

Class 類是 Java 中用來表示運行時類型信息的對應類。實際上在 Java 中每一個類都有一個 Class 對象,每當咱們編寫而且編譯一個新建立的類就會將相關信息寫到 .class 文件裏。當咱們 new 一個新對象或者引用靜態成員變量時,JVM 中的類加載器子系統會將對應 Class 對象加載到 JVM 中,而後 JVM 再根據這個類型信息相關的 Class 對象建立咱們須要實例對象或者提供靜態變量的引用值。咱們能夠將 Class 類,稱爲類類型,一個 Class 對象,稱爲類類型對象(參考 《Thinking in Java》)。數組

Class 類有如下的特色:oracle

  1. Class 類也是類的一種,class 則是關鍵字。
  2. Class 類只有一個私有的構造函數,只有 JVM 可以建立 Class 類的實例。
  3. 對於同一類的對象,在 JVM 中只有惟一一個對應的 Class 類實例來描述其類型信息。(同一個類:即包名 + 類名相同,且由同一個類加載器加載)

.class 文件存儲了一個 Class 的全部信息,好比全部的方法,全部的構造函數,全部的字段(成員屬性)等等。JVM 啓動的時候經過 .class 文件會將相關的類加載到內存中,過程以下: 框架

)

獲取 Class 實例的方法

上面提到 Class 類只有一個私有的構造函數。因此沒法經過 new 的方法獲取 Class 實例。函數

/* * Constructor. Only the Java Virtual Machine creates Class * objects. */
private Class() {}
複製代碼
Class.forName() 方法

能夠經過 Class 的 forName 方法獲取 Class 實例,其中類的名稱要寫類的完整路徑。該方法只能用於獲取引用類型的類類型對象。性能

// 這種方式會使用當前的類的加載器加載,而且會將 Class 類實例初始化
Class<?> clazz = Class.forName("java.lang.String");
// 上面的調用方式等價於
Class<?> clazz = Class.forName("java.lang.String", true, currentLoader);
複製代碼

使用該方法可能會拋出 ClassNotFoundException 異常,這個異常發生在類的加載階段,緣由以下:spa

  1. 類加載器在類路徑中沒有找到該類(檢查:查看所在加載的類以及其所依賴的包是否在類路徑下)
  2. 該類已經被某個類加載器加載到 JVM 內存中,另一個類加載器又嘗試從同一個包中加載
Object.getClass() 方法

若是咱們有一個類的對象,那麼咱們能夠經過 Object.getClass 方法得到該類的 Class 對象。插件

// String 對象的 getClass 方法
Class clazz1 = "hello".getClass();
// 數組對象的 getClass 方法
Class clazz2 = (new byte[1024]).getClass();
System.out.println(class2) // 會輸出 [B, [ 表明是數組, B 表明是 byte。即 byte 數組的類類型
複製代碼
class 語法

若咱們知道要獲取的類類型的名稱時,咱們可使用 class 語法獲取該類類型的對象。

// 類
Class clazz = Integer.class;
// 數組
Class clazz2 = int [][].class;
複製代碼
包裝類的 TYPE 靜態屬性

對於基本類型和 void 都有對應的包裝類。在包裝類中有一個靜態屬性 TYPE,保存了該來的類類型。以 Integer 類爲例,其源碼中定義了以下的靜態屬性:

/** * The {@code Class} instance representing the primitive type * {@code int}. * * @since JDK1.1 */
@SuppressWarnings("unchecked")
public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int");
複製代碼

生成 Class 類實例的方法:

Class clazz1 = Integer.TYPE;
Class clazz2 = Void.TYPE;
複製代碼
Class 類的方法

Class 中有獲取其餘 Class 的方法,列舉以下:

  1. Class.getSuperclass():獲取該類的父類
  2. Class.getClasses() :獲取該類全部公共類、接口、枚舉組成的Class 數組,包括繼承的
  3. Class.getDeclaredClasses():獲取該類顯式聲明的全部類、接口、枚舉組成的 Class 數組
  4. (Class/Field/Method/Constructor).getDeclaringClass():獲取該類/屬性/方法/構造函數所在的類

Member & AccessibleObject

在講 Field、Method、Constructor 以前,先說說 Member 和 AccessibleObject。Member 是一個接口,表示 Class 的成員,前面的三個類都是其實現類。

AccessibleObject 是 Field、Method、Constructor 三個類共同繼承的父類,它提供了將反射的對象標記爲在使用時取消默認 Java 語言訪問控制檢查的能力。經過 setAccessible 方法能夠忽略訪問級別,從而訪問對應的內容。而且 AccessibleObject 實現了 AnnotatedElement 接口,提供了與獲取註解相關的能力。

Field

Field 提供了有關類或接口的單個屬性的信息,以及對它的動態訪問的能力。

能夠經過 Class 提供的方法,獲取 Field 對象,具體以下:

方法返回值 方法名稱 方法說明
Field getDeclaredField(String name) 獲取指定name名稱的(包含private修飾的)字段,不包括繼承的字段
Field[] getDeclaredField() 獲取Class對象所表示的類或接口的全部(包含private修飾的)字段,不包括繼承的字段
Field getField(String name) 獲取指定name名稱、具備public修飾的字段,包含繼承字段
Field[] getField() 獲取修飾符爲public的字段,包含繼承字段

Field 相關的 API 我就不全局列舉出來了,能夠點擊這裏查看

Method

Method 提供了有關類或接口的單個方法的信息,以及對它的動態訪問的能力。

能夠經過 Class 提供的方法,獲取 Field 對象,具體以下:

方法返回值 方法名稱 方法說明
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 返回一個指定參數的Method對象,該對象反映此 Class 對象所表示的類或接口的指定已聲明方法。
Method[] getDeclaredMethod() 返回 Method 對象的一個數組,這些對象反映此 Class 對象表示的類或接口聲明的全部方法,包括公共、保護、默認(包)訪問和私有方法,但不包括繼承的方法。
Method getMethod(String name, Class<?>... parameterTypes) 返回一個 Method 對象,它反映此 Class 對象所表示的類或接口的指定公共成員方法。
Method[] getMethods() 返回一個包含某些 Method 對象的數組,這些對象反映此 Class 對象所表示的類或接口(包括那些由該類或接口聲明的以及從超類和超接口繼承的那些的類或接口)的公共 member 方法。

Method 相關的 API 能夠點擊這裏查看

Constructor

Constructor 提供了有關類的構造方法的信息,以及對它的動態訪問的能力。

能夠經過 Class 提供的方法,獲取 Constructor 對象,具體以下:

方法返回值 方法名稱 方法說明
Constructor<T> getConstructor(Class<?>... parameterTypes) 返回指定參數類型、具備public訪問權限的構造函數對象
Constructor<?>[] getConstructors() 返回全部具備public訪問權限的構造函數的Constructor對象數組
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 返回指定參數類型、全部聲明的(包括private)構造函數對象
Constructor<?>[] getDeclaredConstructor() 返回全部聲明的(包括private)構造函數對象

Constructor 相關的 API 我就不全局列舉出來了,能夠點擊這裏查看

Array

在 Java 中數組也是一種類,Array 提供了動態建立數組和訪問數組元素的靜態方法。

經過 getXXX(Object array, int index) 方法,傳入數組對象和下標索引,能夠獲取到該位置的值。

經過 newInstance(Class<?> componentType, int length) 方法,傳入數組類型和長度,建立數組。以下:

// 下面建立的兩個數組等價
int x[] = new int[10];
int y[] = (int[]) Array.newInstance(int.class, 10);
// 輸出 true
System.out.println(x.length == y.length);
複製代碼

經過 newInstance(Class<?> componentType, int... dimensions) 方法,建立多維數組。以下:

// 下面建立的兩個數組等價
int x[][] = new int[10][10];
int y[][] = (int[][]) Array.newInstance(int.class, 10, 10);
// 輸出 true
System.out.println(x.length == y.length);
複製代碼

反射的應用場景

幾乎全部的 Java 框架都會使用到反射,例如動態配置:讀取寫好的配置文件的值,而後經過反射機制將這些值設置到配置類中。總的來講應用場景有以下幾點:

  1. 框架開發中使用,例如動態配置
  2. 插件開發中使用,例如持續集成
  3. 應用擴展

固然反射也有一些缺點:

  1. 性能低
  2. 可讀性差
  3. 只能在運行期間報錯
相關文章
相關標籤/搜索