反射的基本原理

『反射』就是指程序在運行時可以動態的獲取到一個類的類型信息的一種操做。它是現代框架的靈魂,幾盡全部的框架可以提供的一些自動化機制都是靠反射實現的,這也是爲何各種框架都不容許你覆蓋掉默認的無參構造器的緣由,由於框架須要以反射機制利用無參構造器建立實例。java

總的來講,『反射』是很值得你們花時間學習的,儘管大部分人都不多有機會去手寫框架,可是這將有助於你對於各種框架的理解。不奢求你經過本篇文章的學習對於『反射』可以有多麼深層次的理解,但至少保證你瞭解『反射』的基本原理及使用。git

Class 類型信息

之間介紹過虛擬機的類加載機制,其中咱們提到過,每一種類型都會在初次使用時被加載進虛擬機內存的『方法區』中,包含類中定義的屬性字段,方法字節碼等信息。github

Java 中使用類 java.lang.Class 來指向一個類型信息,經過這個 Class 對象,咱們就能夠獲得該類的全部內部信息。而獲取一個 Class 對象的方法主要有如下三種。數組

類名.class安全

這種方式就比較簡單,只要使用類名點 class 便可獲得方法區該類型的類型信息。例如:微信

Object.class;
Integer.class;
int.class;
String.class;
//等等

getClass 方法框架

Object 類有這麼一個方法:學習

public final native Class<?> getClass();

這是一個本地方法,而且不容許子類重寫,因此理論上全部類型的實例都具備同一個 getClass 方法。具體使用上也很簡單:code

Integer integer = new Integer(12);
integer.getClass();

forName 方法component

forName 算是獲取 Class 類型的一個最經常使用的方法,它容許你傳入一個全類名,該方法會返回方法區表明這個類型的 Class 對象,若是這個類尚未被加載進方法區,forName 會先進行類加載。

public static Class<?> forName(String className) {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

因爲方法區 Class 類型信息由類加載器和類全限定名惟一肯定,因此想要去找這麼一個 Class 就必須提供類加載器和類全限定名,這個 forName 方法默認使用調用者的類加載器。

固然,Class 類中也有一個 forName 重載,容許你傳入類加載器和類全限定名來匹配方法區類型信息。

public static Class<?> forName(String name, boolean initialize,
ClassLoader loader){
    //.....                                       
}

至此,經過這些方法你能夠獲得任意類的類型信息,該類的全部字段屬性,方法表等信息均可以經過這個 Class 對象進行獲取。

反射字段屬性

Class 中有關獲取字段屬性的方法主要如下幾個:

  • public Field[] getFields():返回該類型的全部 public 修飾的屬性,包括父類的
  • public Field getField(String name):根據字段名稱返回相應的字段
  • public Field[] getDeclaredFields():返回本類型中申明的全部字段,包含非 public 修飾的但不包含父類中的
  • public Field getDeclaredField(String name):同理

固然,一個 Field 實例包含某個類的一個屬性的全部信息,包括字段名稱,訪問修飾符,字段類型。除此以外,Field 還提供了大量的操做該屬性值的方法,經過傳入一個類實例,就能夠直接使用 Field 實例操做該實例的當前字段屬性的值。

例如:

//定義一個待反射類
public class People {
    public String name;
}
Class<People> cls = People.class;
Field name = cls.getField("name");
People people = new People();
name.set(people,"hello");
System.out.println(people.name);

程序會輸出:

hello

其實也很簡單,set 方法會檢索 People 對象是否具備一個 name 表明的字段,若是有將字符串 hello 賦值給該字段便可。

整個 Field 類主要由兩大部分組成,第一部分就是有關該字段屬性的描述信息,例如名稱,類型,外圍類 Class 對象等,第二部分就是大量的 get 和 set 方法用於間接操做任意的外圍類實例的當前屬性值。

反射方法

一樣的,Class 類也提供了四種方法來獲取其中的方法屬性:

  • public Method[] getMethods():返回全部的 public 方法,包括父類中的
  • public Method getMethod(String name, Class<?>... parameterTypes):返回指定的方法
  • public Method[] getDeclaredMethods():返回本類申明的全部方法,包括非 public 修飾的,但不包括父類中的
  • public Method getDeclaredMethod(String name, Class<?>... parameterTypes):同理

Method 抽象地表明瞭一個方法,一樣有描述這個方法基本信息的字段和方法,例如方法名,方法的參數集合,方法的返回值類型,異常類型集合,方法的註解等。

除此以外的還有一個 invoke 方法用於間接調用其餘實例的該方法,例如:

public class People {

    public void sayHello(){
        System.out.println("hello wrold ");
    }
}
Class<People> cls = People.class;
Method sayHello = cls.getMethod("sayHello");
People people = new People();
sayHello.invoke(people);

程序輸出:

hello wrold

反射構造器

對於 Constructor 來講,Class 類依然爲它提供了四種獲取實例的方法:

  • public Constructor<?>[] getConstructors():返回全部 public 修飾的構造器
  • public Constructor<?>[] getDeclaredConstructors():返回全部的構造器,無視訪問修飾符
  • public Constructor getConstructor(Class<?>... parameterTypes):帶指定參數的
  • public Constructor getDeclaredConstructor(Class<?>... parameterTypes) :同理

Constructor 本質上也是一個方法,只是沒有返回值而已,因此內部的基本內容和 Method 是相似的,只不過 Constructor 類中有一個 newInstance 方法用於建立一個該 Class 類型的實例對象出來。

//最簡單的一個反射建立實例的過程
Class<People> cls = People.class;
Constructor c = cls.getConstructor();
People p = (People) c.newInstance();

以上,咱們簡單的介紹了反射的基本使用狀況,但都很基礎,下面咱們看看反射和一些稍微複雜的類型結合使用的狀況,例如:數組,泛型,註解等。

反射的其餘細節

反射與數組

咱們都知道,數組是一種特殊的類型,它本質上由虛擬機在運行時動態生成,因此在反射這種類型的時候會稍有不一樣。

public native Class<?> getComponentType();

Class 中有這麼一個方法,該方法將返回數組 Class 實例元素的基本類型。只有當前的 Class 對象表明的是一個數組類型的時候,該方法纔會返回數組的元素實際類型,其餘的任什麼時候候都會返回 null。

固然,有一點須要注意下,表明數組的這個由虛擬機動態建立的類型,它直接繼承的 Object 類,而且全部有關數組類的操做,好比爲某個元素賦值或是獲取數組長度的操做都直接對應一個單獨的虛擬機數組操做指令。

一樣也由於數組類直接由虛擬機運行時動態建立,因此你不可能從一個數組類型的 Class 實例中獲得構造方法,編譯器根本沒機會爲類生成默認的構造器。因而你也不能以常規的方法經過 Constructor 來建立一個該類的實例對象。

若是你非要嘗試使用 Constructor 來建立一個新的實例的話,那麼運行時程序將告訴你沒法匹配一個構造器。像這樣:

Class<String[]> cls = String[].class;
Constructor constructor = cls.getConstructor();
String[] strs = (String[]) constructor.newInstance();

控制檯輸出:

image

告訴你,Class 實例中根本找不到一個無參的構造器。那麼難道咱們就沒有辦法來動態建立一個數組了嗎?

固然不是,Java 中有一個類 java.lang.reflect.Array 提供了一些靜態的方法用於動態的建立和獲取一個數組類型。

//建立一個一維數組,componentType 爲數組元素類型,length 數組長度
public static Object newInstance(Class<?> componentType, int length)

//可變參數 dimensions,指定多個維度的單維度長度
public static Object newInstance(Class<?> componentType, int... dimensions)

這是我認爲 Array 類中最重要的兩個方法,固然了 Array 類中還有一些其它方法用於獲取指定數組的指定位置元素,這裏再也不贅述了。

徹底是由於數組這種類型並非由常規的編譯器編譯生成,而是由虛擬機動態建立的,因此想要經過反射的方式實例化一個數組類型是得依賴 Array 這個類型的相關 newInstance 方法的。

反射與泛型

泛型是 Java 編譯器範圍內的概念,它可以在程序運行以前提供必定的安全檢查,而反射是運行時發生的,也就是說若是你反射調用一個泛型方法,實際上就繞過了編譯器的泛型檢查了。咱們看一段代碼:

ArrayList<Integer> list = new ArrayList<>();
list.add(23);
//list.add("fads");編譯不經過

Class<?> cls = list.getClass();
Method add = cls.getMethod("add",Object.class);
add.invoke(list,"hello");
System.out.println(list.get(1));

最終你會發現咱們從整型容器中取出一個字符串,由於虛擬機只管在運行時從方法區找到 ArrayList 這個類的類型信息並解析出它的 add 方法,接着執行這個方法。

它不像通常的方法調用,調用以前編譯器會檢測這個方法存在不存在,參數類型是否匹配等,因此沒了編譯器的這層安全檢查,反射地調用方法更容易遇到問題。

除此以外,以前咱們說過的泛型在通過編譯期以後會被類型擦除,但實際上表明該類型的 Class 類型信息中是保存有一些基本的泛型信息的,這一點咱們能夠經過反射獲得。

這裏再也不帶你們一塊兒去看了,Class ,Field 和 Method 中都是有相關方法能夠獲取類或者方法在定義的時候所使用到的泛型類名名稱。注意這裏說的,只是名稱,相似 E、V 這樣的東西。


文章中的全部代碼、圖片、文件都雲存儲在個人 GitHub 上:

(https://github.com/SingleYam/overview_java)

歡迎關注微信公衆號:OneJavaCoder,全部文章都將同步在公衆號上。

image

相關文章
相關標籤/搜索