【轉】JAVA反射與(yu)注解
2018-04-25 11:56 xiashengwang 閱讀(3188) 評論(1) 收藏 舉報轉載自:
前言
現在在我們構建自己或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,比如:、、、等,如果你沒用過,那你需要找時間補一下啦;有時在使用后我們會好奇他們到底是怎么做到這種簡潔、高效、松耦合等諸多優點的,當然這里我不探討它們具體怎么實現的 (可以看看我之前寫的) ,而關心的是它們都用到同樣的技術那就是本篇所講的反射和注解,并實現的依賴注入。
閱(yue)讀本(ben)篇文章有助于你更好的理解這些大形(xing)框架的原理和復(fu)習Java的知識點。為(wei)什么要把(ba)反射(she)放在前面講呢,實際(ji)上是因為(wei)我們學(xue)習注解的時候需要用(yong)到反射(she)機(ji)制,所以(yi),先學(xue)習反射(she)有助于理解后面的知識。
JAVA反射
主要(yao)是指程序可以訪問,檢(jian)測和修改(gai)它本身(shen)狀態或行(xing)為的一種能力(li),并(bing)能根據自身(shen)行(xing)為的狀態和結(jie)果,調整或修改(gai)應用所描(miao)述行(xing)為的狀態和相關的語義。
反射機制是什么
面試(shi)有可能(neng)會問到,這(zhe)句(ju)話不管你能(neng)不能(neng)理(li)解,但(dan)是你只要記住就可以(yi)了
反射機制就是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性;這(zhe)種動態(tai)獲(huo)取(qu)的信息(xi)以及動態(tai)調用對(dui)象的方法的功能稱為java語言的反射機制。
用一句話總結就是反射可以實現在運行時可以知道任意一個類的屬性和方法。
反射機制能做什么
反射(she)機制主(zhu)要提供了(le)以(yi)下功能:
- 在運行時判斷任意一個對象所屬的類;
- 在運行時構造任意一個類的對象;
- 在運行時判斷任意一個類所具有的成員變量和方法;
- 在運行時調用任意一個對象的方法;
- 生成(ps:這個知識點也很重要,后續會為大家講到)
Java 反射機制的應用場景
- 逆向代碼 ,例如反編譯
- 與注解相結合的框架 例如Retrofit
- 單純的反射機制應用框架 例如EventBus
- 動態生成類框架 例如Gson
反射機制的優點與缺點
為什么要用反射(she)機制?直接創(chuang)建對象不就可以了(le)(le)嗎,這(zhe)就涉及到了(le)(le)動態與靜態的概念(nian)
-
靜(jing)態編譯:在編譯時確(que)定類型,綁定對(dui)象,即通(tong)過(guo)。
-
動(dong)態(tai)編譯(yi):運行時確定類(lei)型,綁定對象。動(dong)態(tai)編譯(yi)最大限度發揮了java的靈(ling)活(huo)性,體現(xian)了多態(tai)的應用,有以降低類(lei)之間(jian)的藕(ou)合(he)性。
優點
- 可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。比如,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯后,發布了,當發現需要更新某些功能時,我們不可能要用戶把以前的卸載,再重新安裝新的版本,假如這樣的話,這個軟件肯定是沒有多少人用的。采用靜態的話,需要把整個程序重新編譯一次才可以實現功能的更新,而采用反射機制的話,它就可以不用卸載,只需要在運行時才動態的創建和編譯,就可以實現該功能。
缺點
- 對性能有影響。使用反射基本上是一種解釋操作,我們可以告訴JVM,我們希望做什么并且它滿足我們的要求。這類操作總是慢于只直接執行相同的操作。
理解Class類和類類型
想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是所有類的類(There is a class named Class)
對(dui)于普通的對(dui)象,我們一(yi)般都會這樣創建和表(biao)示(shi):
1
|
Code code1 = new Code();
|
上(shang)面(mian)說了,所有的類都是(shi)Class的對象,那么(me)如何表示呢(ni),可(ke)不可(ke)以通過如下(xia)方式(shi)呢(ni):
1
|
Class c = new Class();
|
但是我們查看(kan)Class的源碼時(shi),是這樣(yang)寫(xie)的:
1
|
private Class(ClassLoader loader) {
|
可以看(kan)到構造器是私有的,只有JVM可以創建(jian)Class的對(dui)象(xiang),因此不可以像(xiang)普通類(lei)一(yi)樣new一(yi)個Class對(dui)象(xiang),雖然我(wo)們不能new一(yi)個Class對(dui)象(xiang),但是卻(que)可以通過已有的類(lei)得到一(yi)個Class對(dui)象(xiang),共有三種方式,如(ru)下:
1
|
Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是通過獲取類的靜態成員變量class得到的
|
這里,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這(zhe)(zhe)里就(jiu)(jiu)讓(rang)人奇怪了(le)(le),前(qian)面(mian)不是(shi)(shi)說Code是(shi)(shi)Class的(de)對象嗎,而c1、c2、c3也是(shi)(shi)Class的(de)對象,那(nei)么(me)Code和(he)c1、c2、c3不就(jiu)(jiu)一(yi)(yi)樣了(le)(le)嗎?為什么(me)還叫Code什么(me)類(lei)(lei)(lei)類(lei)(lei)(lei)型(xing)(xing)(xing)(xing)?這(zhe)(zhe)里不要糾結于它(ta)們是(shi)(shi)否相同(tong),只要理解類(lei)(lei)(lei)類(lei)(lei)(lei)型(xing)(xing)(xing)(xing)是(shi)(shi)干(gan)什么(me)的(de)就(jiu)(jiu)好了(le)(le),顧名思(si)義(yi),類(lei)(lei)(lei)類(lei)(lei)(lei)型(xing)(xing)(xing)(xing)就(jiu)(jiu)是(shi)(shi)類(lei)(lei)(lei)的(de)類(lei)(lei)(lei)型(xing)(xing)(xing)(xing),也就(jiu)(jiu)是(shi)(shi)描述一(yi)(yi)個(ge)類(lei)(lei)(lei)是(shi)(shi)什么(me),都有(you)哪些東西,所以(yi)我們可(ke)以(yi)通(tong)過(guo)類(lei)(lei)(lei)類(lei)(lei)(lei)型(xing)(xing)(xing)(xing)知(zhi)道一(yi)(yi)個(ge)類(lei)(lei)(lei)的(de)屬(shu)性和(he)方法,并且(qie)可(ke)以(yi)調用一(yi)(yi)個(ge)類(lei)(lei)(lei)的(de)屬(shu)性和(he)方法,這(zhe)(zhe)就(jiu)(jiu)是(shi)(shi)反射的(de)基礎。
舉個簡單例子代碼:
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) // 得到該類所有的方法,不包括父類的
|
舉個例子:
例如類(lei)A有如下一(yi)個(ge)方(fang)法:
1
|
public void fun(String name,int age) {
|
現在知(zhi)道(dao)A有(you)一個對象a,那么(me)就可以通(tong)過:
1
|
Class c = Class.forName("com.tengj.reflect.Person"); //先生成class
|
完整代碼如下:
1
|
public class Person {
|
執行結果:
我叫tengj,今年10歲
怎樣,是不是感覺很厲害,我(wo)們只要知道這(zhe)個類的路徑全(quan)稱就能(neng)玩弄它(ta)于鼓掌之(zhi)間。
有(you)時候我們(men)想(xiang)獲取類中所有(you)成員方法(fa)的信息,要怎么(me)辦(ban)。可以通過以下幾步來實現(xian):
1.獲取所有方法(fa)的數組:
1
|
Class c = Class.forName("com.tengj.reflect.Person");
|
2.然(ran)后循環這個(ge)數組就得到每個(ge)方法了:
1
|
for (Method method : methods)
|
完整代碼如下:
person類跟上面一樣,這里以及(ji)后面就不貼(tie)(tie)出(chu)來了,只貼(tie)(tie)關鍵代碼
1
|
public class ReflectDemo {
|
執行結果:
getName
setName
setAge
fun
fun
getAge
這(zhe)里如果(guo)把(ba)c.getDeclaredMethods();改成(cheng)c.getMethods();執行(xing)結果(guo)如下(xia),多了很多方(fang)(fang)法,以(yi)為把(ba)Object里面的(de)方(fang)(fang)法也(ye)打印出來了,因(yin)為Object是(shi)所有類的(de)父(fu)類:
getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
-
獲取成員變量信息
想一想成員變量中都包括什么:成員變量類型+成員變量名
類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,所以我們通過java.lang.reflect.Field里面封裝的(de)方法(fa)來獲(huo)取這些信(xin)息。
單(dan)獨獲取(qu)某個成員(yuan)變量(liang),通過Class類的(de)以下方(fang)法實現:
參數是成員變量的名字
1
|
public Field getDeclaredField(String name) // 獲得該類自身聲明的所有變量,不包括其父類的變量
|
舉個例子:
例如(ru)一個類(lei)A有(you)如(ru)下成員變量:
1
|
private int n;
|
如果(guo)A有一個(ge)對象a,那么就可以這樣得到(dao)其成員變量:
1
|
Class c = a.getClass();
|
完整代碼如下:
1
|
public class ReflectDemo {
|
執行結果:
hello wrold
同(tong)樣,如果想要獲取所有成員(yuan)變量的(de)信(xin)息,可以通過以下幾步
1.獲(huo)取所有成員(yuan)變量的(de)數組:
1
|
Field[] fields = c.getDeclaredFields();
|
2.遍歷(li)變量數組,獲得某個成員變量field
1
|
for (Field field : fields)
|
完整代碼:
1
|
public class ReflectDemo {
|
執行結果:
name
age
msg
-
獲取構造函數
最后再想一想構造函數中都包括什么:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,所以我們通過java.lang.reflect.Constructor里面封裝的方法來獲取這(zhe)些信(xin)息。
單獨獲取某個構造函數,通過Class類的以下方法實現:
這個參數為構造函數參數類的類類型列表
1
|
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 獲得該類所有的構造器,不包括其父類的構造器
|
舉個例子:
例如類A有(you)如下(xia)一個(ge)構造函(han)數:
1
|
public A(String a, int b) {
|
那么就可以通過:
1
|
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);
|
來獲取這個(ge)構造函數(shu)。
完整代碼:
1
|
public class ReflectDemo {
|
執行結果:
tengj
注意(yi):Class的newInstance方法,只能創(chuang)建只包含無參數的構造函(han)數的類,如果某類只有帶參數的構造函(han)數,那(nei)么就要(yao)使用另(ling)外一種方式:
1
|
fromClass.getDeclaredConstructor(String.class).newInstance("tengj");
|
獲(huo)取所有的構造函數,可以(yi)(yi)通過以(yi)(yi)下步(bu)驟實現:
1.獲取該類(lei)的所(suo)有(you)構造函數,放在一(yi)個數組中(zhong):
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();//判斷是否是基礎類型
|
通過反射了解集合泛型的本質
擴(kuo)展的知識點,了解就可以了。后續會為大家寫(xie)一篇關于(yu)泛型(xing)的文章(zhang)。
首先下結論:
Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。
下面通(tong)過一個實例來驗證:
1
|
/**
|
執行結果:
list2的長度是:1
true
list2的長度是:2
思維導圖
有助于理解上述所講(jiang)的知識點
拓展閱讀
Java反射機制深入詳解 - 火星十一郎 - 博客園
Java反射機制 - ①塊腹肌 - 博客園
Java 反射機制淺析 - 孤旅者 - 博客園
Java動態代理與反射詳解 - 浩大王 - 博客園
JAVA注解
概念及作用
- 概念
- 注解即元數據,就是源代碼的元數據
- 注解在代碼中添加信息提供了一種形式化的方法,可以在后續中更方便的 使用這些數據
- Annotation是一種應用于類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。
- 作用
- 生成文檔
- 跟蹤代碼依賴性,實現替代配置文件功能,減少配置。如Spring中的一些注解
- 在編譯時進行格式檢查,如@Override等
- 每當你創建描述符性質的類或者接口時,一旦其中包含重復性的工作,就可以考慮使用注解來簡化與自動化該過程。
什么是java注解?
在java語法中,使用@符號作為開(kai)頭,并在@后面緊(jin)跟(gen)注解名。被(bei)運(yun)用于類(lei),接口,方(fang)法(fa)和字(zi)段(duan)之上,例(li)如:
1
|
|
這(zhe)其中@Override就(jiu)是注解(jie)。這(zhe)個注解(jie)的(de)作用也就(jiu)是告訴編譯器(qi),myMethod()方法覆(fu)寫(xie)了父類(lei)中的(de)myMethod()方法。
java中內置的注解
java中有三個內(nei)置的(de)注解(jie):
- @Override:表示當前的方法定義將覆蓋超類中的方法,如果出現錯誤,編譯器就會報錯。
- @Deprecated:如果使用此注解,編譯器會出現警告信息。
- @SuppressWarnings:忽略編譯器的警告信息。
本文不(bu)在闡述(shu)三種(zhong)內置注解(jie)的(de)使(shi)用情節和方法(fa),感興趣的(de)請看
元注解
自定義注解的時候用到的,也就是自定義注解的注解;(這(zhe)句話我自己說(shuo)的,不知道對(dui)不對(dui))
元注解的作用就是負責注解其他注解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對(dui)其(qi)它 annotation類型作說明。
Java5.0定義的4個元注解:
-
@Target
-
@Retention
-
@Documented
-
@Inherited
java8加了兩(liang)個新注(zhu)解,后續我會講(jiang)到(dao)。
這些類型和(he)它們(men)所(suo)支持的類在java.lang.annotation包中可以(yi)找到。
@Target
@Target說明(ming)(ming)了(le)Annotation所修飾的對象范圍(wei):Annotation可被用(yong)于 packages、types(類、接口、枚舉(ju)、Annotation類型)、類型成員(方(fang)法、構造方(fang)法、成員變(bian)(bian)量、枚舉(ju)值)、方(fang)法參數(shu)和本地(di)變(bian)(bian)量(如(ru)循環變(bian)(bian)量、catch參數(shu))。在(zai)Annotation類型的聲明(ming)(ming)中使(shi)用(yong)了(le)target可更加明(ming)(ming)晰其修飾的目標。
作用:用于描述注解的使用范圍(即:被描述的注解可以用在什么地方)
取值(ElementType)有:
| 類型 | 用途 |
|---|---|
| CONSTRUCTOR | 用于描述構造器 |
| FIELD | 用于描述域 |
| LOCAL_VARIABLE | 用于描述局部變量 |
| METHOD | 用于描述方法 |
| PACKAGE | 用于描述包 |
| PARAMETER | 用于描述參數 |
| TYPE | 用于描述類、接口(包括注解類型) 或enum聲明 |
比如(ru)說這個(ge)注解表示只能在方法(fa)中使(shi)用(yong):
1
|
|
@Retention
@Retention定(ding)義(yi)了該Annotation被(bei)保(bao)留的(de)(de)時間長短:某些Annotation僅出現在(zai)(zai)源代碼中,而被(bei)編(bian)譯(yi)器丟(diu)棄;而另(ling)(ling)一些卻被(bei)編(bian)譯(yi)在(zai)(zai)class文件(jian)中;編(bian)譯(yi)在(zai)(zai)class文件(jian)中的(de)(de)Annotation可(ke)(ke)能會(hui)被(bei)虛擬機忽略,而另(ling)(ling)一些在(zai)(zai)class被(bei)裝載時將被(bei)讀取(請注意并不影響(xiang)class的(de)(de)執行,因為Annotation與class在(zai)(zai)使(shi)用(yong)上是被(bei)分(fen)離(li)的(de)(de))。使(shi)用(yong)這(zhe)個meta-Annotation可(ke)(ke)以對 Annotation的(de)(de)“生(sheng)命周期”限制。
作用:表示需要在什么級別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內有效)
取值(RetentionPoicy)有:
| 類型 | 用途 | 說明 |
|---|---|---|
| SOURCE | 在源文件中有效(即源文件保留) | 僅出現在源代碼中,而被編譯器丟棄 |
| CLASS | 在class文件中有效(即class保留) | 被編譯在class文件中 |
| RUNTIME | 在運行時有效(即運行時保留) | 編譯在class文件中 |
使用示例:
1
|
/***
|
@Documented
@Documented用于(yu)描述其(qi)它(ta)類型的annotation應該被作(zuo)為(wei)被標(biao)注的程序成(cheng)(cheng)員(yuan)的公(gong)共API,因此可以被例如javadoc此類的工(gong)具文(wen)檔化。Documented是一個標(biao)記注解,沒有成(cheng)(cheng)員(yuan)。
作用:將注解包含在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類型被發現,或者到達類繼承結構的頂層。
作用:允許子類繼承父類中的注解
示例,這里的(de)MyParentClass 使用(yong)的(de)注(zhu)(zhu)解(jie)標注(zhu)(zhu)了(le)@Inherited,所以子(zi)類可以繼承這個(ge)注(zhu)(zhu)解(jie)信息:
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接(jie)(jie)口(kou)(kou)來代(dai)(dai)表程(cheng)序元素前(qian)面(mian)的(de)注(zhu)解(jie),該(gai)接(jie)(jie)口(kou)(kou)是所有(you)Annotation類(lei)型的(de)父接(jie)(jie)口(kou)(kou)。除此(ci)之(zhi)外,Java在java.lang.reflect 包下(xia)新(xin)增了(le)AnnotatedElement接(jie)(jie)口(kou)(kou),該(gai)接(jie)(jie)口(kou)(kou)代(dai)(dai)表程(cheng)序中(zhong)可以(yi)接(jie)(jie)受注(zhu)解(jie)的(de)程(cheng)序元素,該(gai)接(jie)(jie)口(kou)(kou)主要有(you)如下(xia)幾個(ge)實現類(lei):
- Class:類定義
- Constructor:構造器定義
- Field:累的成員變量定義
- Method:類的方法定義
- Package:類的包定義
java.lang.reflect 包下主(zhu)要包含一(yi)些(xie)實現反射功能(neng)(neng)的(de)(de)工具類,實際上,java.lang.reflect 包所有(you)提(ti)供的(de)(de)反射API擴充了(le)讀取(qu)運行(xing)時(shi)Annotation信息的(de)(de)能(neng)(neng)力。當一(yi)個Annotation類型被定(ding)義(yi)為運行(xing)時(shi)的(de)(de)Annotation后(hou),該注解才能(neng)(neng)是(shi)運行(xing)時(shi)可見,當class文件被裝載時(shi)被保存在(zai)class文件中(zhong)的(de)(de)Annotation才會(hui)被虛擬機讀取(qu)。
AnnotatedElement 接(jie)口是所有程(cheng)(cheng)序元素(Class、Method和Constructor)的(de)(de)父(fu)接(jie)口,所以程(cheng)(cheng)序通(tong)過反射(she)獲取了(le)某個(ge)類(lei)的(de)(de)AnnotatedElement對象之后,程(cheng)(cheng)序就可以調用該對象的(de)(de)如下四個(ge)個(ge)方法(fa)來訪問Annotation信息:
- 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
- 方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
- 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
- 方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
注解處理器示例:
1
|
/***********注解聲明***************/ |
