轉載自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/
前言
如今在咱們構建本身或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,好比:Butter Knife
、Retrofit 2
、Dagger 2
、GreenDao
等,若是你沒用過,那你須要找時間補一下啦;有時在使用後咱們會好奇他們究竟是怎麼作到這種簡潔、高效、鬆耦合等諸多優勢的,固然這裏我不探討它們具體怎麼實現的 (能夠看看我以前寫的幾篇文章) ,而關心的是它們都用到一樣的技術那就是本篇所講的反射和註解,並實現的依賴注入。html
閱讀本篇文章有助於你更好的理解這些大形框架的原理和複習Java的知識點。爲何要把反射放在前面講呢,其實是由於咱們學習註解的時候須要用到反射機制,因此,先學習反射有助於理解後面的知識。java
JAVA反射
主要是指程序能夠訪問,檢測和修改它自己狀態或行爲的一種能力,並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。面試
反射機制是什麼
面試有可能會問到,這句話無論你能不能理解,可是你只要記住就能夠了數組
反射機制就是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。app
用一句話總結就是反射能夠實如今運行時能夠知道任意一個類的屬性和方法。框架
反射機制能作什麼
反射機制主要提供瞭如下功能:ide
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象;
- 在運行時判斷任意一個類所具備的成員變量和方法;
- 在運行時調用任意一個對象的方法;
- 生成動態代理(ps:這個知識點也很重要,後續會爲你們講到)
Java 反射機制的應用場景
- 逆向代碼 ,例如反編譯
- 與註解相結合的框架 例如Retrofit
- 單純的反射機制應用框架 例如EventBus
- 動態生成類框架 例如Gson
反射機制的優勢與缺點
爲何要用反射機制?直接建立對象不就能夠了嗎,這就涉及到了動態與靜態的概念函數
-
靜態編譯:在編譯時肯定類型,綁定對象,即經過。工具
-
動態編譯:運行時肯定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以下降類之間的藕合性。post
優勢
- 能夠實現動態建立對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。好比,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯後,發佈了,當發現須要更新某些功能時,咱們不可能要用戶把之前的卸載,再從新安裝新的版本,假如這樣的話,這個軟件確定是沒有多少人用的。採用靜態的話,須要把整個程序從新編譯一次才能夠實現功能的更新,而採用反射機制的話,它就能夠不用卸載,只須要在運行時才動態的建立和編譯,就能夠實現該功能。
缺點
- 對性能有影響。使用反射基本上是一種解釋操做,咱們能夠告訴JVM,咱們但願作什麼而且它知足咱們的要求。這類操做老是慢於只直接執行相同的操做。
理解Class類和類類型
想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是全部類的類(There is a class named Class)
對於普通的對象,咱們通常都會這樣建立和表示:
1 |
Code code1 = new Code(); |
上面說了,全部的類都是Class的對象,那麼如何表示呢,可不能夠經過以下方式呢:
1 |
Class c = new Class(); |
可是咱們查看Class的源碼時,是這樣寫的:
1 |
private Class(ClassLoader loader) { |
能夠看到構造器是私有的,只有JVM能夠建立Class的對象,所以不能夠像普通類同樣new一個Class對象,雖然咱們不能new一個Class對象,可是卻能夠經過已有的類獲得一個Class對象,共有三種方式,以下:
1 |
Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是經過獲取類的靜態成員變量class獲得的 |
這裏,c一、c二、c3都是Class的對象,他們是徹底同樣的,並且有個學名,叫作Code的類類型(class type)。
這裏就讓人奇怪了,前面不是說Code是Class的對象嗎,而c一、c二、c3也是Class的對象,那麼Code和c一、c二、c3不就同樣了嗎?爲何還叫Code什麼類類型?這裏不要糾結於它們是否相同,只要理解類類型是幹什麼的就行了,顧名思義,類類型就是類的類型,也就是描述一個類是什麼,都有哪些東西,因此咱們能夠經過類類型知道一個類的屬性和方法,而且能夠調用一個類的屬性和方法,這就是反射的基礎。
舉個簡單例子代碼:
1 |
public class ReflectDemo { |
執行結果:
1 |
com.tengj.reflect.ReflectDemo |
Java反射相關操做
在這裏先看一下sun爲咱們提供了那些反射機制中的類:
java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;
前面咱們知道了怎麼獲取Class,那麼咱們能夠經過這個Class幹什麼呢?
總結以下:
- 獲取成員方法Method
- 獲取成員變量Field
- 獲取構造函數Constructor
下面來具體介紹
-
獲取成員方法信息
兩個參數分別是方法名和方法參數類的類類型列表。
1 |
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 獲得該類全部的方法,不包括父類的 |
舉個例子:
例如類A有以下一個方法:
1 |
public void fun(String name,int age) { |
如今知道A有一個對象a,那麼就能夠經過:
1 |
Class c = Class.forName("com.tengj.reflect.Person"); //先生成class |
完整代碼以下:
1 |
public class Person { |
執行結果:
我叫tengj,今年10歲
怎樣,是否是感受很厲害,咱們只要知道這個類的路徑全稱就能玩弄它於鼓掌之間。
有時候咱們想獲取類中全部成員方法的信息,要怎麼辦。能夠經過如下幾步來實現:
1.獲取全部方法的數組:
1 |
Class c = Class.forName("com.tengj.reflect.Person"); |
2.而後循環這個數組就獲得每一個方法了:
1 |
for (Method method : methods) |
完整代碼以下:
person類跟上面同樣,這裏以及後面就不貼出來了,只貼關鍵代碼
1 |
public class ReflectDemo { |
執行結果:
getName setName setAge fun fun getAge
這裏若是把c.getDeclaredMethods();改爲c.getMethods();執行結果以下,多了不少方法,覺得把Object裏面的方法也打印出來了,由於Object是全部類的父類:
getName setName getAge setAge fun fun wait wait wait equals toString hashCode getClass notify notifyAll
-
獲取成員變量信息
想想成員變量中都包括什麼:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field
的一個對象,因此咱們經過java.lang.reflect.Field
裏面封裝的方法來獲取這些信息。
單獨獲取某個成員變量,經過Class類的如下方法實現:
參數是成員變量的名字
1 |
public Field getDeclaredField(String name) // 得到該類自身聲明的全部變量,不包括其父類的變量 |
舉個例子:
例如一個類A有以下成員變量:
1 |
private int n; |
若是A有一個對象a,那麼就能夠這樣獲得其成員變量:
1 |
Class c = a.getClass(); |
完整代碼以下:
1 |
public class ReflectDemo { |
執行結果:
hello wrold
一樣,若是想要獲取全部成員變量的信息,能夠經過如下幾步
1.獲取全部成員變量的數組:
1 |
Field[] fields = c.getDeclaredFields(); |
2.遍歷變量數組,得到某個成員變量field
1 |
for (Field field : fields) |
完整代碼:
1 |
public class ReflectDemo { |
執行結果:
name age msg
-
獲取構造函數
最後再想想構造函數中都包括什麼:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor
的一個對象,因此咱們經過java.lang.reflect.Constructor
裏面封裝的方法來獲取這些信息。
單獨獲取某個構造函數,經過Class
類的如下方法實現:
這個參數爲構造函數參數類的類類型列表
1 |
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 得到該類全部的構造器,不包括其父類的構造器 |
舉個例子:
例如類A有以下一個構造函數:
1 |
public A(String a, int b) { |
那麼就能夠經過:
1 |
Constructor constructor = a.getDeclaredConstructor(String.class, int.class); |
來獲取這個構造函數。
完整代碼:
1 |
public class ReflectDemo { |
執行結果:
tengj
注意:Class的newInstance方法,只能建立只包含無參數的構造函數的類,若是某類只有帶參數的構造函數,那麼就要使用另一種方式:
1 |
fromClass.getDeclaredConstructor(String.class).newInstance("tengj"); |
獲取全部的構造函數,能夠經過如下步驟實現:
1.獲取該類的全部構造函數,放在一個數組中:
1 |
Constructor[] constructors = c.getDeclaredConstructors(); |
2.遍歷構造函數數組,得到某個構造函數constructor
:
1 |
for (Constructor constructor : constructors) |
完整代碼:
1 |
public class ReflectDemo { |
執行結果:
public com.tengj.reflect.Person() public com.tengj.reflect.Person(java.lang.String)
-
其餘方法
註解須要用到的
1 |
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//獲取class對象的全部註解 |
獲取class對象的信息
1 |
boolean isPrimitive = class1.isPrimitive();//判斷是不是基礎類型 |
經過反射了解集合泛型的本質
擴展的知識點,瞭解就能夠了。後續會爲你們寫一篇關於泛型的文章。
首先下結論:
Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。
下面經過一個實例來驗證:
1 |
/** |
執行結果:
list2的長度是:1 true list2的長度是:2
思惟導圖
有助於理解上述所講的知識點
拓展閱讀
Java反射機制深刻詳解 - 火星十一郎 - 博客園
Java反射入門 - Trigl的博客 - CSDN博客
Java反射機制 - ①塊腹肌 - 博客園
Java 反射機制淺析 - 孤旅者 - 博客園
反射機制的理解及其用途 - 天天進步一點點! - ITeye博客
Java動態代理與反射詳解 - 浩大王 - 博客園
JAVA註解
概念及做用
- 概念
- 註解即元數據,就是源代碼的元數據
- 註解在代碼中添加信息提供了一種形式化的方法,能夠在後續中更方便的 使用這些數據
- Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。
- 做用
- 生成文檔
- 跟蹤代碼依賴性,實現替代配置文件功能,減小配置。如Spring中的一些註解
- 在編譯時進行格式檢查,如@Override等
- 每當你建立描述符性質的類或者接口時,一旦其中包含重複性的工做,就能夠考慮使用註解來簡化與自動化該過程。
什麼是java註解?
在java語法中,使用@
符號做爲開頭,並在@後面緊跟註解名。被運用於類,接口,方法和字段之上,例如:
1 |
|
這其中@Override就是註解。這個註解的做用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。
java中內置的註解
java中有三個內置的註解:
- @Override:表示當前的方法定義將覆蓋超類中的方法,若是出現錯誤,編譯器就會報錯。
- @Deprecated:若是使用此註解,編譯器會出現警告信息。
- @SuppressWarnings:忽略編譯器的警告信息。
本文不在闡述三種內置註解的使用情節和方法,感興趣的請看這裏
元註解
自定義註解的時候用到的,也就是自定義註解的註解;(這句話我本身說的,不知道對不對)
元註解的做用就是負責註解其餘註解。Java5.0
定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型做說明。
Java5.0
定義的4個元註解:
-
@Target
-
@Retention
-
@Documented
-
@Inherited
java8加了兩個新註解,後續我會講到。
這些類型和它們所支持的類在java.lang.annotation包中能夠找到。
@Target
@Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。
做用:用於描述註解的使用範圍(即:被描述的註解能夠用在什麼地方)
取值(ElementType)有:
類型 | 用途 |
---|---|
CONSTRUCTOR | 用於描述構造器 |
FIELD | 用於描述域 |
LOCAL_VARIABLE | 用於描述局部變量 |
METHOD | 用於描述方法 |
PACKAGE | 用於描述包 |
PARAMETER | 用於描述參數 |
TYPE | 用於描述類、接口(包括註解類型) 或enum聲明 |
好比說這個註解表示只能在方法中使用:
1 |
|
@Retention
@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出如今源代碼中,而被編譯器丟棄;而另外一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另外一些在class被裝載時將被讀取(請注意並不影響class的執行,由於Annotation與class在使用上是被分離的)。使用這個meta-Annotation能夠對 Annotation的「生命週期」限制。
做用:表示須要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)
取值(RetentionPoicy)有:
類型 | 用途 | 說明 |
---|---|---|
SOURCE | 在源文件中有效(即源文件保留) | 僅出如今源代碼中,而被編譯器丟棄 |
CLASS | 在class文件中有效(即class保留) | 被編譯在class文件中 |
RUNTIME | 在運行時有效(即運行時保留) | 編譯在class文件中 |
使用示例:
1 |
/*** |
@Documented
@Documented用於描述其它類型的annotation應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。
做用:將註解包含在javadoc中
示例:
1 |
java.lang.annotation.Documented |
@Inherited
- 是一個標記註解
- 闡述了某個被標註的類型是被繼承的
- 使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類
@Inherited annotation類型是被標註過的class的子類所繼承。類並不從實現的接口繼承annotation,方法不從它所重載的方法繼承annotation
- 當@Inherited annotation類型標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API加強了這種繼承性。若是咱們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工做:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。
做用:容許子類繼承父類中的註解
示例,這裏的MyParentClass 使用的註解標註了@Inherited,因此子類能夠繼承這個註解信息:
1 |
java.lang.annotation.Inherited |
1 |
|
1 |
public class MyChildClass extends MyParentClass { |
自定義註解
格式
1 |
public |
註解參數的可支持數據類型:
- 全部基本數據類型(int,float,double,boolean,byte,char,long,short)
- String 類型
- Class類型
- enum類型
- Annotation類型
- 以上全部類型的數組
規則
- 修飾符只能是public 或默認(default)
- 參數成員只能用基本類型byte,short,int,long,float,double,boolean八種基本類型和String,Enum,Class,annotations及這些類型的數組
- 若是隻有一個參數成員,最好將名稱設爲」value」
- 註解元素必須有肯定的值,能夠在註解中定義默認值,也可使用註解時指定,非基本類型的值不可爲null,常使用空字符串或0做默認值
- 在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字符串或負值
示例:
1 |
/** |
註解處理器類庫
java.lang.reflect.AnnotatedElement
Java使用Annotation接口來表明程序元素前面的註解,該接口是全部Annotation類型的父接口。除此以外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口表明程序中能夠接受註解的程序元素,該接口主要有以下幾個實現類:
- Class:類定義
- Constructor:構造器定義
- Field:累的成員變量定義
- Method:類的方法定義
- Package:類的包定義
java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包全部提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。
AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的以下四個個方法來訪問Annotation信息:
- 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程序元素上存在的全部註解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。
註解處理器示例:
1 |
/***********註解聲明***************/ |