一蜜猾、注解(Annotation)
1.什么是注解?
相信大家對注解應(yīng)該并不陌生振诬,在現(xiàn)在信息飛速發(fā)展的年代蹭睡,各種優(yōu)秀的框架或許都離不開注解的使用,像我們在實現(xiàn)接口一個方法時赶么,也會有@Override注解肩豁。注解說白了就是對程序做出解釋,與我們在方法辫呻、類上的注釋沒有區(qū)別清钥,但是注解可以被其他程序所讀取,進行信息處理放闺,否則與注釋沒有太大的區(qū)別祟昭。
2.內(nèi)置注解
內(nèi)置注解就是我們的jdk所帶的一些注解。常用的三個注解:
@Override
這個應(yīng)該都不陌生怖侦,修辭方法篡悟,表示打算重寫超類中的方法聲明谜叹。
@Deprecated
這個注解我們應(yīng)該也不會陌生,我們可能看不到這個注解恰力,但是我們肯定在使用一些方法時會出現(xiàn)橫線叉谜。表示廢棄旗吁,這個注釋可以修辭方法踩萎,屬性,類很钓,表示不鼓勵程序員使用這樣的元素香府,通常是因為他很危險或有更好的選擇。
@SuperWarnings
這個注解主要是用來抑制警告信息的码倦,我們在寫程序時企孩,可能會報很多黃線的警告,但是不影響運行袁稽,我們就可以用這個注解來抑制隱藏它勿璃。與前倆個注解不同的是我們必須給注解參數(shù)才能正確使用他。
參數(shù)? ? ? ? ? ? ? ?說明
deprecation 使用了過時的類或方法的警告
unchecked 執(zhí)行了未檢查的轉(zhuǎn)換時的警告 如:使用集合時未指定泛型
fallthrough 當在switch語句使用時發(fā)生case穿透
path? ?在類路徑推汽、源文件路徑中有不存在路徑的警告
serial? 當在序列化的類上缺少serialVersionUID定義時的警告
finally? 任何finally子句不能完成時的警告
all? 關(guān)于以上所有的警告
上表中就是@SuperWarnings注解的一些參數(shù)补疑,按需使用即可。
@SuperWarnings(“finally”)
@SuperWarnings(value={“unchecked”,“path”})
3.自定義注解
格式:public @interface 注解名 { 定義體 }
使用@interface自定義注解時歹撒,自動繼承了java.lang.annotation.Annotation接口
其中的每一個方法實際上是聲明了一個配置參數(shù)
方法的名稱就是參數(shù)的名稱
返回值類型就是參數(shù)的類型(返回值類型只能是基本類型莲组、Class、String暖夭、enum)
可以通過default來聲明參數(shù)的默認值
如果只有一個參數(shù)成員锹杈,一般參數(shù)名為value
我們在使用注解元素時必須要有值,可以定義默認值迈着,空字符串竭望,0或者-1
public @interface TestAnnotation {
? ? //參數(shù)默認為空
? ? String value() default "";
}
4.元注解
我們在自定義注解時,需要使用java提供的元注解裕菠,就是負責注解的其他注解市框。java定義了四個標準的meta-annotation類型,他們被用來提供對其他注解類型聲明糕韧。
@Target
這個注解的作用主要是用來描述注解的使用范圍枫振,說白了就是我們自己定義的注解可以使用在哪個地方。
所修飾范圍 取值ElementType
package 包 PACKAGE
類萤彩、接口粪滤、枚舉、Annotation類型 TYPE
類型成員(方法雀扶,構(gòu)造方法杖小,成員變量肆汹,枚舉值) CONSTRUCTOR:用于描述構(gòu)造器。FIELD:用于描述域予权。METHOD:用于描述方法
方法參數(shù)和本地變量 LOCAL_VARIABLE:用于描述局部變量昂勉。PARAMETER:用于描述參數(shù)
我們自定義一個注解,在聲明元注解時可以看到提供我們的所有常量扫腺。我們以ElementType.METHOD為例岗照。
@Target(ElementType.METHOD)
public @interface TestAnnotation {
//參數(shù)默認為空
? ? String value() default "";
}
我們在來測試一下這個注解
結(jié)果顯而易見,當我們將注解放在我們的變量時笆环,編譯器給我們報了一個錯攒至,翻譯過來的意思就是這個注解不被允許放在這里。而放在方法上就可以安靜的放在那里躁劣。
@Retention
這個注解的作用就是我們需要告訴編譯器我們需要在什么級別保存該注釋信息迫吐,用于描述注解的生命周期。
取值RetentionPolicy 作用
SOURCE 在源文件中有效(即源文件保留)
CLASS 在class文件中有效(即class保留)
RUNTIME 在運行時有效(即運行時保留)注:為RUNTIME時可以被反射機制所讀取
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
//參數(shù)默認為空
? ? String value() default "";
}
在一般情況下我們使用RUNTIME即可账忘。這樣在程序運行時我們也可以通過反射機制來讀取到該注解志膀。
@Document
@Inherited
上面?zhèn)z個注解我們使用的就不算很多了,大家有興趣可以自行百度一下鳖擒,與生成文檔樹有關(guān)好像溉浙。
我們一會可以通過反射機制讀取到我們的注解烤黍。
二觅捆、反射(Reflect)
1.什么是反射?
反射指的是我們可以在運行期間加載嚷量、探知圆裕、使用編譯期間完全未知的類广鳍。是一個動態(tài)的機制,允許我們通過字符串來指揮程序?qū)嵗抛保僮鲗傩陨奘薄⒄{(diào)用方法。使得代碼提高了靈活性行拢,但是同時也帶來了更多的資源開銷祖秒。
加載完類之后,在堆內(nèi)存中舟奠,就產(chǎn)生了一個 Class 類型的對象(一個 類只有一個 Class 對象)竭缝,這個對象就包含了完整的類的結(jié)構(gòu)信息。 我們可以通過這個對象看到類的結(jié)構(gòu)沼瘫。這個對象就像一面鏡子抬纸,透過 這個鏡子看到類的結(jié)構(gòu),所以耿戚,我們形象的稱之為:反射湿故。
2.class類
我們在使用反射時阿趁,需要先獲得我們需要的類,而java.lang.Class這個類必不可少坛猪,他十分特殊脖阵,用來表示java中類型 (class/interface/enum/annotation/primitive type/void)本身。
Class類的對象包含了某個被加載類的結(jié)構(gòu)墅茉。一個被加載的類對應(yīng)一個 Class對象命黔。
當一個class被加載,或當加載器(class loader)的defineClass()被 JVM調(diào)用躁锁,JVM 便自動產(chǎn)生一個Class 對象纷铣。
我們應(yīng)該如何獲取Class類的對象卵史?
我們先創(chuàng)建一個普通的實體類战转。
package sml.reflect;
public class User {
? ? //這里name用的是私有類型
? ? private String name;
? ? //這里age用的是公有類型
? ? public int age;
//無參構(gòu)造器
? ? public User(){}
? ? //有參構(gòu)造器
? ? public User(String name, int age) {
? ? ? ? this.name = name;
? ? ? ? this.age = age;
? ? }
? ? public String getName() {
? ? ? ? return name;
? ? }
? ? public void setName(String name) {
? ? ? ? this.name = name;
? ? }
? ? public int getAge() {
? ? ? ? return age;
? ? }
? ? public void setAge(int age) {
? ? ? ? this.age = age;
? ? }
}
通過Class.forName()獲取(最常用)
public class TestReflect {
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? //獲取User的Class對象以躯,參數(shù)為需要獲取類對象的全類名
? ? ? ? ? Class aClass = Class.forName("sml.reflect.User");
? ? ? ? //因為是動態(tài)編譯槐秧,所有我們需要拋出類未找到的異常?
? ? ? ? } catch (ClassNotFoundException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
通過getClass()獲取
public class TestReflect {
? ? public static void main(String[] args) {
? ? ? ? //new一個user對象
? ? ? ? User user = new User();
? ? ? ? //通過user對象來獲取User類對象
? ? ? ? Class aClass = user.getClass();
? ? }
}
通過.class獲取
public class TestReflect {
? ? public static void main(String[] args) {
? ? ? ? //通過導(dǎo)包獲取類名點class來獲取類對象
? ? ? ? Class aClass = User.class;
? ? }
}
3.反射的基本操作
我們獲取到Class對象后,可以獲取該類的某些信息忧设。在這里我們來看一些常用的刁标。
1)獲取類名
Class aClass = Class.forName("sml.reflect.User");
//獲取全類名
String name = aClass.getName();
//獲取簡單的類名
String simpleName = aClass.getSimpleName();
結(jié)果:
2)獲取類的字段、某些變量
Class aClass = Class.forName("sml.reflect.User");
//獲取該類的所有public字段址晕,包括父類的
Field[] fields = aClass.getFields();
//根據(jù)字段名獲取該類的public字段
Field field = aClass.getField("age");
//獲取該類的所有字段膀懈,不包括父類(僅自定義)
Field[] fields1 = aClass.getDeclaredFields();
//根據(jù)字段名獲取該類的字段
Field field1 = aClass.getDeclaredField("name");
注意:我們仔細看注釋,不帶Declared的方法職能獲取到public字段谨垃,且包括父類的启搂,帶Declared的方法可以獲取到所有的自定義的字段!
測試一下:
3)獲取類的方法
Class aClass = Class.forName("sml.reflect.User");
//獲取該類的所有public方法刘陶,包括父類的
Method[] methods = aClass.getMethods();
//根據(jù)方法名獲取該類的public方法
Method method = aClass.getMethod("getName");
//如果該類為重寫方法胳赌,可以在第二個參數(shù)加上重寫方法的參數(shù)類型,不寫為無參數(shù)的方法
Method paramMethod = aClass.getMethod("getName",String.class)
//獲取該類的所有方法匙隔,不包括父類(僅自定義)
Method[] declaredMethods = aClass.getDeclaredMethods();
//根據(jù)方法名獲取該類的方法
Method declaredMethod = aClass.getDeclaredMethod("getName");
注:獲取方法的方式與獲取字段的方法一樣疑苫,在這里我們需要注意的是重寫的方法,一個類中存在倆個或多個方法名是一樣的纷责,因此在根據(jù)方法名獲取方法時捍掺,提供第二個參數(shù),為可變參數(shù)再膳。參數(shù)類型為我們獲取方法的參數(shù)類型的類挺勿,如果不寫默認為無參方法。
4)獲取類的構(gòu)造器
Class aClass = Class.forName("sml.reflect.User");
//獲取該類的所有構(gòu)造器饵史,包括父類
Constructor[] constructors = aClass.getConstructors();
//根據(jù)構(gòu)造器的參數(shù)類型來獲取指定構(gòu)造器满钟,不寫為無參構(gòu)造器
Constructor constructor = aClass.getConstructor();
Constructor constructor1 = aClass.getConstructor(String.class,int.class);
//獲取該類的所有構(gòu)造器胜榔,不包括父類
Constructor[] declaredConstructors = aClass.getDeclaredConstructors();
//根據(jù)構(gòu)造器的參數(shù)類型來獲取指定的自定義構(gòu)造器,不寫為無參構(gòu)造器
Constructor declaredConstructor = aClass.getDeclaredConstructor();
Constructor declaredConstructor1 = aClass.getDeclaredConstructor(String.class, int.class);
注:在我們獲取類構(gòu)造器和類方法時涉及到可變參數(shù)的知識湃番,大家可以自行百度一下夭织,或者查閱官方文檔,也不難吠撮,就是在我們不確定參數(shù)有幾個時尊惰,就可以寫成可變參數(shù),我們在使用時可以傳多個參數(shù)泥兰。注意我們寫的參數(shù)都為類對象弄屡!
我們獲取到構(gòu)造器第一想法應(yīng)該是實例化對象。
5)類的實例化
Class aClass = Class.forName("sml.reflect.User");
//通過class類直接實例化鞋诗,使用的是User類的無參構(gòu)造器
User user = (User) aClass.newInstance();
//獲取構(gòu)造器來進行實例化,這里獲取有參構(gòu)造器
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
//根據(jù)構(gòu)造器進行實例化
User user1 = (User) declaredConstructor.newInstance("sml",18);
注:我們在使用類對象直接實例化時膀捷,一定要確保需實例化的類中存在無參構(gòu)造器,否則會報錯削彬。默認獲取的是Object類型全庸,因此最后需要進行下類型轉(zhuǎn)化。
測試:我們在User類中重寫toString方法打印下獲取的對象看一下
我們獲取到對象是不是該通過獲取的對象調(diào)用方法融痛,NO壶笼!我們通過反射來調(diào)用對象的方法。
6)方法的調(diào)用
Class aClass = Class.forName("sml.reflect.User");
User user = (User) aClass.newInstance();
//獲取setName方法
Method setName = aClass.getDeclaredMethod("setName", String.class);
//通過獲取的方法來調(diào)用(invoke),invoke方法有倆個參數(shù)
//第一個是調(diào)用底層方法的對象雁刷,也就是通過哪個對象來調(diào)用方法
//第二個為可變參數(shù)覆劈,是用于方法調(diào)用的參數(shù)
setName.invoke(user,"sml");
測試:我們打印下該對象看一下方法執(zhí)行了沒有
注:如果我們調(diào)用的方法為私有方法,雖然編譯器通過沛励,在運行時會報錯的(java.lang.IllegalAccessException)责语,這是因為java的安全檢查。我們可以使用setAccessible(true)這個方法來跳過安全檢查侯勉。
Class aClass = Class.forName("sml.reflect.User");
User user = (User) aClass.newInstance();
Method setName = aClass.getDeclaredMethod("setName", String.class);
//若setName為私有方法鹦筹,跳過安全檢查
setName.setAccessible(true);
setName.invoke(user,"sml");
我們在寫程序時一般通過getset方法來操作字段,下面我們同樣也是通過反射來操作字段址貌。
7)字段的操作
Class aClass = Class.forName("sml.reflect.User");
User user = (User) aClass.newInstance();
//獲取name字段
Field name = aClass.getDeclaredField("name");
//通過該字段的set方法來改變該字段的值铐拐,該字段有倆個參數(shù)
//第一個為應(yīng)該修改其字段的參數(shù)
//第二個為被修改字段的新值
name.set(user,"sml");
測試:我們打印下該對象,看一下name字段改變沒有
呀练对,報錯了遍蟋。看這個錯眼熟不螟凭,我們在上面說過虚青,這是因為我們的name字段為私有,我們不能直接去操作該字段螺男,需要跳過安全檢查棒厘,我們加上name.setAccessible(true)纵穿;再來運行一下。
這還不夠奢人,反射很強谓媒,我們說過反射的對象像一面鏡子,我們能看到的東西都可以獲取何乎,我們下面來讀取一下參數(shù)泛型句惯,返回值泛型!
8)泛型的操作(Generic)
對于泛型我們應(yīng)該不會陌生支救,java采用泛型擦除的機制來引入泛型抢野。也就是說java的泛型僅僅是給編譯器javac使用的,確保數(shù)據(jù)的安全性和免去強制類型轉(zhuǎn)換的麻煩各墨。但是一旦編譯完成指孤,所有和泛型有關(guān)的數(shù)據(jù)全部擦除。
為了通過反射操作這些類型以迎合實際開發(fā)的需要欲主,Java就新增了ParameterizedType邓厕, GenericArrayType逝嚎,TypeVariable 和WildcardType幾種類型來代表不能被歸一到Class 類中的類型但是又和原始類型齊名的類型扁瓢,這四種類型實現(xiàn)了Type接口。
類型 含義
ParameterizedType 參數(shù)化類型补君,帶有類型參數(shù)的類型引几,即常說的泛型,如:List《T》
TypeVariable 類型變量挽铁,如參數(shù)化類型Map《E伟桅,Y》中的Y、K等類型變量叽掘,表示泛指任何類
GenericArrayType (泛型)數(shù)組類型楣铁,比如List《T》[],T[]這種更扁。注意盖腕,這不是我們說的一般數(shù)組,而是表示一種【元素類型是參數(shù)化類型或者類型變量的】數(shù)組類型
WildcardType 代表通配符表達式浓镜,或泛型表達式溃列,比如【?】【? super T】【? extends T】。雖然WildcardType是Type的一個子接口膛薛,但并不是Java類型中的一種
我們演示一下反射讀取ParameterizedType類型听隐。
public class TestReflect {
? ? //測試方法,返回類型與參數(shù)都為泛型
? ? public static Map<String,User> GenericityTest(List<User> list,User user){
? ? ? ? return null;
? ? }
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? //先獲取到該類
? ? ? ? ? ? Class aClass = Class.forName("sml.reflect.TestReflect");
? ? ? ? ? ? //獲取到測試方法
? ? ? ? ? ? Method genericityTest = aClass.getDeclaredMethod("GenericityTest", List.class,User.class);
? ? ? ? ? ? //獲取到類型參數(shù)數(shù)組,就是獲取方法所有的參數(shù)類型
? ? ? ? ? ? Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
? ? ? ? ? ? for (Type genericParameterType : genericParameterTypes) {
? ? ? ? ? ? ? ? //輸出一下類型參數(shù)
? ? ? ? ? ? ? ? System.out.println(genericParameterType);
? ? ? ? ? ? ? ? //我們在循環(huán)時判斷該參數(shù)類型哄啄,若該參數(shù)屬于參數(shù)化類型
? ? ? ? ? ? ? ? if(genericParameterType instanceof ParameterizedType){
? ? ? ? ? ? ? ? ? ? //若屬于參數(shù)化類型雅任,則獲取類型對象的數(shù)組风范,表示此類型的實際類型參數(shù)
? ? ? ? ? ? ? ? ? ? Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
? ? ? ? ? ? ? ? ? ? for (Type actualTypeArgument : actualTypeArguments) {
? ? ? ? ? ? ? ? ? ? ? ? //打印下實際類型參數(shù)
? ? ? ? ? ? ? ? ? ? ? ? System.out.println(actualTypeArgument);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
我們看著可能會很復(fù)雜,我們來分析一下沪么。
首先乌企,genericityTest這個為我們獲取的方法,我們通過genericityTest來獲取該方法的參數(shù)成玫,注意看加酵,這里返回的是Type類型,也就是所有類型的父接口哭当,我們打印下看猪腕,就是返回的List與User,也就是他的倆個參數(shù)List《User》,User钦勘。
接下來我們遍歷下類型Type參數(shù)數(shù)組陋葡,我們現(xiàn)在需要的是讀取參數(shù)化類型,那么我們對每一個Type參數(shù)進行判斷彻采,如果該Type參數(shù)屬于ParameterizedType參數(shù)化類型腐缤,那么我們在獲取到該泛型的實際類型參數(shù),也就是List中的User肛响,注意岭粤,只有List《User》才屬于參數(shù)化類型,可以查看上面的表特笋。
注:我們在上面演示的只是獲取方法的參數(shù)剃浇,那么我們?nèi)绾潍@取返回值的類型?下面第二個方法
//獲取方法所有的參數(shù)類型
Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
//獲取返回值的參數(shù)類型猎物,返回值只有一個虎囚,所有不是數(shù)組
Type genericReturnType = genericityTest.getGenericReturnType();
9)注解的操作
注解的操作相對就比較簡單了,如果我們想讀取類上蔫磨、方法上或字段上的注解淘讥,我們僅需要獲取到你需要讀取的注解所修辭的類、方法或字段來獲取就可以堤如。
我們以獲取方法上的注解來測試一下:
這里我們還是使用我們在文章首部創(chuàng)建的TestAnnotation注解蒲列,只能放在方法上,保留到運行時煤惩。
public class AnnotationTest {
? ? @TestAnnotation("sml")
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? Class aClass = Class.forName("sml.annotation.AnnotationTest");
? ? ? ? ? ? Method main = aClass.getDeclaredMethod("main",String[].class);
? ? ? ? ? ? //根據(jù)我們的main方法獲取main方法上的注解
? ? ? ? ? ? TestAnnotation declaredAnnotation = main.getDeclaredAnnotation(TestAnnotation.class);
? ? ? ? ? ? //獲取到他的值
? ? ? ? ? ? String value = declaredAnnotation.value();
? ? ? ? ? ? System.out.println(value);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
注:如果我們需要獲取類上的注解嫉嘀,只需要獲取到類對象,然后.getDeclaredAnnotation()即可魄揉,其實不管是獲取類上的注解還是字段上的注解都是一樣的方法剪侮,關(guān)于有無Declared的方法,看到這里大家應(yīng)該也有了解了。
最后說一下瓣俯,到這里我們可能沒有體會到注解的太大作用杰标,在后面的文章我會寫一篇手寫SpringMVC框架的博客,當然只是簡單到不能簡單的版本彩匕。如果將這篇文章看會腔剂,在自己寫一下,關(guān)于注解與反射我相信大家應(yīng)該都會的差不多了驼仪。
————————————————
版權(quán)聲明:本文為CSDN博主「世代農(nóng)民」的原創(chuàng)文章掸犬,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明绪爸。
原文鏈接:https://blog.csdn.net/weixin_45056780/article/details/105127722