吃透Java基礎六:反射

一:什麼是反射

Java 反射機制可以讓我們在編譯期(Compile Time)之外的運行期(Runtime)檢查類,接口,變量以及方法的信息。反射還可以讓我們在運行期實例化對象,調用方法,通過調用 get/set 方法獲取變量的值。

很多人都認為反射在實際的 Java 開發應用中並不廣泛,其實不然。當我們在使用 IDE(如 Eclipse,IDEA)時,當我們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裡就會用到反射。

反射最重要的用途就是開發各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過 XML 文件配置 Bean),為了保證框架的通用性,它們可能需要根據配置文件加載不同的對象或類,調用不同的方法,這個時候就必須用到反射,運行時動態加載需要加載的對象。

二:反射應用

使用 Java 反射機制可以在運行時期檢查 Java 類的信息,檢查 Java 類的信息往往是你在使用 Java 反射機制的時候所做的第一件事情,通過獲取類的信息你可以獲取以下相關的內容:**Class對象、類名、修飾符、包信息、父類、實現的接口、構造器、方法、變量、註解**等等。

1、Class 對象

檢查一個類的信息之前,你首先需要獲取類的 Class 對象。Java 中的所有類型包括基本類型(int, long, float等等),即使是數組都有與之關聯的 Class 類的對象,獲取Class對象有如下三種方式:

吃透Java基礎六:反射

2、類名

可以從 Class 對象中獲取兩個版本的類名

吃透Java基礎六:反射

3、修飾符

可以通過 Class 對象來訪問一個類的修飾符, 即public,private,static 等等的關鍵字,你可以使用如下方法來獲取類的修飾符:

吃透Java基礎六:反射

修飾符都被包裝成一個int類型的數字,這樣每個修飾符都是一個位標識(flag bit),這個位標識可以設置和清除修飾符的類型。 可以使用 java.lang.reflect.Modifier 類中的方法來檢查修飾符的類型:

吃透Java基礎六:反射

4、包信息

可以使用 Class 對象通過如下的方式獲取包信息:

吃透Java基礎六:反射

通過 Package 對象你可以獲取包的相關信息,比如包名。

5、父類

通過 Class 對象你可以訪問類的父類

吃透Java基礎六:反射

可以得到 superclass 對象其實就是一個 Class 類的實例,所以你可以繼續在這個對象上進行反射操作。

6、實現的接口

可以通過如下方式獲取指定類所實現的接口集合:

吃透Java基礎六:反射

由於一個類可以實現多個接口,因此 getInterfaces(); 方法返回一個 Class 數組,在 Java 中接口同樣有對應的 Class 對象。 注意:getInterfaces() 方法僅僅只返回當前類所實現的接口。當前類的父類如果實現了接口,這些接口是不會在返回的 Class 集合中的,儘管實際上當前類其實已經實現了父類接口。

7、構造器

吃透Java基礎六:反射

8、變量

使用 Java 反射機制你可以運行期檢查一個類的變量信息(成員變量)或者獲取或者設置變量的值。通過使用 java.lang.reflect.Field 類就可以實現上述功能

吃透Java基礎六:反射

field.setAccessible(true)這行代碼,通過調用 setAccessible()方法會關閉指定類 Field 實例的反射訪問檢查,這行代碼執行之後不論是私有的、受保護的以及包訪問的作用域,你都可以在任何地方訪問,即使你不在他的訪問權限作用域之內。但是你如果你用一般代碼來訪問這些不在你權限作用域之內的代碼依然是不可以的,在編譯的時候就會報錯。

9、方法

吃透Java基礎六:反射

Method.invoke(Object target, Object … parameters)方法第一個參數是你要調用方法的對象,如果是一個靜態方法調用的話則可以用 null 代替指定對象作為 invoke()的參數,在上面這個例子中,如果 doSomething 不是靜態方法的話,你就要傳入有效的 MyObject 實例而不是 null。 Method.invoke(Object target, Object … parameters)方法的第二個參數是一個可變參數列表,但是你必須要傳入與你要調用方法的形參一一對應的實參。就像上個例子那樣,方法需要 String 類型的參數,那我們必須要傳入一個字符串。

10、註解

註解是 Java 5 的一個新特性。註解是插入你代碼中的一種註釋或者說是一種元數據(meta data)。這些註解信息可以在編譯期使用預編譯工具進行處理(pre-compiler tools),也可以在運行期使用 Java 反射機制進行處理,下面定義一個MyAnnotation註解:

吃透Java基礎六:反射

在 interface 前面的@符號表名這是一個註解,一旦你定義了一個註解之後你就可以將其應用到你的代碼中,就像之前我們的那個例子那樣。 在註解定義中的兩個指示@Retention(RetentionPolicy.RUNTIME)和@Target(ElementType.TYPE),說明了這個註解該如何使用。 @Retention(RetentionPolicy.RUNTIME)表示這個註解可以在運行期通過反射訪問。如果你沒有在註解定義的時候使用這個指示那麼這個註解的信息不會保留到運行期,這樣反射就無法獲取它的信息。 @Target(ElementType.TYPE) 表示這個註解只能用在類型上面(比如類跟接口)。你同樣可以把Type改為Field或者Method,或者你可以不用這個指示,這樣的話你的註解在類,方法和變量上就都可以使用了。

吃透Java基礎六:反射

11、泛型

我常常在一些文章以及論壇中讀到說 Java 泛型信息在編譯期被擦除所以你無法在運行期獲得有關泛型的信息。其實這種說法並不完全正確的,在一些情況下是可以在運行期獲取到泛型的信息。

Java 在編譯時會在字節碼裡指令集之外的地方保留部分泛型信息,泛型接口、類、方法定義上的所有泛型、成員變量聲明處的泛型都會被保留類型信息,其他地方的泛型信息都會被擦除。

Java 的泛型機制雖然在編譯期間進行了擦除,但是在編譯 Java 源代碼成 class 文件中還是保存了泛型相關的信息,這些信息被保存在 class 字節碼的常量池中,使用了泛型的代碼處會生成一個 signature 簽名字段,通過簽名 signature 字段指明這個常量池的地址,JDK 提供了方法去讀取這些泛型信息的方法,然後再借助反射就可以獲得泛型參數的具體類型

所以獲取泛型參數類型的實質就是通過 Class 類的 getGenericSuperClass() 方法返回一個 ParameterizedType 對象(對於 Object、接口和原始類型返回 null,對於數組 class 返回 Object.class),ParameterizedType 表示帶有泛型參數類型的 Java 類型,JDK1.5 引入泛型後 Java 中所有的 Class 都實現了 Type 接口,ParameterizedType 繼承了 Type 接口,所有包含泛型的 Class 類都會自動實現這個接口。

泛型的擦除機制實際上擦除的是除結構化信息外的所有東西(結構化信息指與類結構相關的信息,而不是與程序執行流程有關的,即與類及其字段和方法的類型參數相關的元數據都會被保留下來通過反射獲取到)。

吃透Java基礎六:反射

  • 泛型方法返回類型

如果你獲得了 java.lang.reflect.Method 對象,那麼你就可以獲取到這個方法的泛型返回類型信息

吃透Java基礎六:反射

輸出:class java.lang.String

  • 泛型方法參數類型
吃透Java基礎六:反射

輸出:class java.lang.String

  • 泛型變量類型
吃透Java基礎六:反射

輸出:class java.lang.String

12、數組

Java 反射機制通過 java.lang.reflect.Array 這個類來處理數組。

吃透Java基礎六:反射

13、動態代理

利用Java反射機制你可以在運行期動態的創建接口的實現。 java.lang.reflect.Proxy 類就可以實現這一功能。這個類的名字(譯者注:Proxy 意思為代理)就是為什麼把動態接口實現叫做動態代理。動態的代理的用途十分廣泛,比如數據庫連接和事物管理(transaction management)還有單元測試時用到的動態 mock 對象以及 AOP 中的方法攔截功能等等都使用到了動態代理。

創建代理

可以通過使用 Proxy.newProxyInstance()方法創建動態代理。 newProxyInstance()方法有三個參數: 1、類加載器(ClassLoader)用來加載動態代理類。 2、一個要實現的接口的數組。 3、一個 InvocationHandler 把所有方法的調用都轉到代理上。

吃透Java基礎六:反射

invoke()方法中的 Method 對象參數代表了被動態代理的接口中要調用的方法,從這個 method 對象中你可以獲取到這個方法名字,方法的參數,參數類型等等信息。

Object 數組參數包含了被動態代理的方法需要的方法參數。注意:原生數據類型(如int,long等等)方法參數傳入等價的包裝對象(如Integer, Long等等)。

吃透Java基礎六:反射

執行完這段代碼之後,變量 proxy 包含一個 MyInterface 接口的的動態實現。所有對 proxy 的調用都被轉向到實現了 InvocationHandler 接口的 handler 上。


分享到:


相關文章: