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