Java 反射是一個比較重要的知識點,你會在不少地方見到反射。它提供了 Java 語言在運行期間加載、探知和使用編譯期間徹底未知的類的能力。這種能力在框架的編寫中很是常見,例如動態代理中、類掃描解析中。html
反射機制:即 Java 語言在運行時有一種自觀的能力,可以瞭解自身的狀況併爲下一步的動做作準備。反應出來就是,在運行時,對於一個類,咱們可以知道該類有哪些方法和屬性。對於一個對象,咱們可以調用其任意的一個方法。這是一種動態獲取類的信息以及動態調用對象方法的能力。java
Java 提供反射機制,依賴於 Class 類和 java.lang.reflect 類庫。其主要的類以下:api
Class 類是 Java 中用來表示運行時類型信息的對應類。實際上在 Java 中每一個類都有一個 Class 對象,每當咱們編寫而且編譯一個新建立的類就會將相關信息寫到 .class 文件裏。當咱們 new 一個新對象或者引用靜態成員變量時,JVM 中的類加載器子系統會將對應 Class 對象加載到 JVM 中,而後 JVM 再根據這個類型信息相關的 Class 對象建立咱們須要實例對象或者提供靜態變量的引用值。咱們能夠將 Class 類,稱爲類類型,一個 Class 對象,稱爲類類型對象(參考 《Thinking in Java》)。數組
Class 類有如下的特色:oracle
.class 文件存儲了一個 Class 的全部信息,好比全部的方法,全部的構造函數,全部的字段(成員屬性)等等。JVM 啓動的時候經過 .class 文件會將相關的類加載到內存中,過程以下: 框架
)上面提到 Class 類只有一個私有的構造函數。因此沒法經過 new 的方法獲取 Class 實例。函數
/* * Constructor. Only the Java Virtual Machine creates Class * objects. */
private Class() {}
複製代碼
能夠經過 Class 的 forName 方法獲取 Class 實例,其中類的名稱要寫類的完整路徑。該方法只能用於獲取引用類型的類類型對象。性能
// 這種方式會使用當前的類的加載器加載,而且會將 Class 類實例初始化
Class<?> clazz = Class.forName("java.lang.String");
// 上面的調用方式等價於
Class<?> clazz = Class.forName("java.lang.String", true, currentLoader);
複製代碼
使用該方法可能會拋出 ClassNotFoundException 異常,這個異常發生在類的加載階段,緣由以下:spa
若是咱們有一個類的對象,那麼咱們能夠經過 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 clazz = Integer.class;
// 數組
Class clazz2 = int [][].class;
複製代碼
對於基本類型和 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 的方法,列舉以下:
在講 Field、Method、Constructor 以前,先說說 Member 和 AccessibleObject。Member 是一個接口,表示 Class 的成員,前面的三個類都是其實現類。
AccessibleObject 是 Field、Method、Constructor 三個類共同繼承的父類,它提供了將反射的對象標記爲在使用時取消默認 Java 語言訪問控制檢查的能力。經過 setAccessible 方法能夠忽略訪問級別,從而訪問對應的內容。而且 AccessibleObject 實現了 AnnotatedElement 接口,提供了與獲取註解相關的能力。
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 提供了有關類或接口的單個方法的信息,以及對它的動態訪問的能力。
能夠經過 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 提供了有關類的構造方法的信息,以及對它的動態訪問的能力。
能夠經過 Class 提供的方法,獲取 Constructor 對象,具體以下:
方法返回值 | 方法名稱 | 方法說明 |
---|---|---|
Constructor<T> |
getConstructor(Class<?>... parameterTypes) |
返回指定參數類型、具備public訪問權限的構造函數對象 |
Constructor<?>[] |
getConstructors() |
返回全部具備public訪問權限的構造函數的Constructor對象數組 |
Constructor<T> |
getDeclaredConstructor(Class<?>... parameterTypes) |
返回指定參數類型、全部聲明的(包括private)構造函數對象 |
Constructor<?>[] |
getDeclaredConstructor() |
返回全部聲明的(包括private)構造函數對象 |
Constructor 相關的 API 我就不全局列舉出來了,能夠點擊這裏查看。
在 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 框架都會使用到反射,例如動態配置:讀取寫好的配置文件的值,而後經過反射機制將這些值設置到配置類中。總的來講應用場景有以下幾點:
固然反射也有一些缺點: