一、注解(Annotation)
1.什么是注解?
相信大家對(duì)注解應(yīng)該并不陌生,在現(xiàn)在信息飛速發(fā)展的年代,各種優(yōu)秀的框架或許都離不開注解的使用,像我們?cè)趯?shí)現(xiàn)接口一個(gè)方法時(shí),也會(huì)有@Override注解。注解說白了就是對(duì)程序做出解釋,與我們?cè)诜椒?、類上的注釋沒有區(qū)別,但是注解可以被其他程序所讀取,進(jìn)行信息處理,否則與注釋沒有太大的區(qū)別。
2.內(nèi)置注解
內(nèi)置注解就是我們的jdk所帶的一些注解。常用的三個(gè)注解:
@Override
這個(gè)應(yīng)該都不陌生,修辭方法,表示打算重寫超類中的方法聲明。
@Deprecated
這個(gè)注解我們應(yīng)該也不會(huì)陌生,我們可能看不到這個(gè)注解,但是我們肯定在使用一些方法時(shí)會(huì)出現(xiàn)橫線。表示廢棄,這個(gè)注釋可以修辭方法,屬性,類,表示不鼓勵(lì)程序員使用這樣的元素,通常是因?yàn)樗芪kU(xiǎn)或有更好的選擇。
@SuperWarnings
這個(gè)注解主要是用來抑制警告信息的,我們?cè)趯懗绦驎r(shí),可能會(huì)報(bào)很多黃線的警告,但是不影響運(yùn)行,我們就可以用這個(gè)注解來抑制隱藏它。與前倆個(gè)注解不同的是我們必須給注解參數(shù)才能正確使用他。
參數(shù)? ? ? ? ? ? ? ?說明
deprecation 使用了過時(shí)的類或方法的警告
unchecked 執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告 如:使用集合時(shí)未指定泛型
fallthrough 當(dāng)在switch語句使用時(shí)發(fā)生case穿透
path? ?在類路徑、源文件路徑中有不存在路徑的警告
serial? 當(dāng)在序列化的類上缺少serialVersionUID定義時(shí)的警告
finally? 任何finally子句不能完成時(shí)的警告
all? 關(guān)于以上所有的警告
上表中就是@SuperWarnings注解的一些參數(shù),按需使用即可。
@SuperWarnings(“finally”)
@SuperWarnings(value={“unchecked”,“path”})
3.自定義注解
格式:public @interface 注解名 { 定義體 }
使用@interface自定義注解時(shí),自動(dòng)繼承了java.lang.annotation.Annotation接口
其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)
方法的名稱就是參數(shù)的名稱
返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)
可以通過default來聲明參數(shù)的默認(rèn)值
如果只有一個(gè)參數(shù)成員,一般參數(shù)名為value
我們?cè)谑褂米⒔庠貢r(shí)必須要有值,可以定義默認(rèn)值,空字符串,0或者-1
public @interface TestAnnotation {
? ? //參數(shù)默認(rèn)為空
? ? String value() default "";
}
4.元注解
我們?cè)谧远x注解時(shí),需要使用java提供的元注解,就是負(fù)責(zé)注解的其他注解。java定義了四個(gè)標(biāo)準(zhǔn)的meta-annotation類型,他們被用來提供對(duì)其他注解類型聲明。
@Target
這個(gè)注解的作用主要是用來描述注解的使用范圍,說白了就是我們自己定義的注解可以使用在哪個(gè)地方。
所修飾范圍 取值ElementType
package 包 PACKAGE
類、接口、枚舉、Annotation類型 TYPE
類型成員(方法,構(gòu)造方法,成員變量,枚舉值) CONSTRUCTOR:用于描述構(gòu)造器。FIELD:用于描述域。METHOD:用于描述方法
方法參數(shù)和本地變量 LOCAL_VARIABLE:用于描述局部變量。PARAMETER:用于描述參數(shù)
我們自定義一個(gè)注解,在聲明元注解時(shí)可以看到提供我們的所有常量。我們以ElementType.METHOD為例。
@Target(ElementType.METHOD)
public @interface TestAnnotation {
//參數(shù)默認(rèn)為空
? ? String value() default "";
}
我們?cè)趤頊y(cè)試一下這個(gè)注解
結(jié)果顯而易見,當(dāng)我們將注解放在我們的變量時(shí),編譯器給我們報(bào)了一個(gè)錯(cuò),翻譯過來的意思就是這個(gè)注解不被允許放在這里。而放在方法上就可以安靜的放在那里。
@Retention
這個(gè)注解的作用就是我們需要告訴編譯器我們需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期。
取值RetentionPolicy 作用
SOURCE 在源文件中有效(即源文件保留)
CLASS 在class文件中有效(即class保留)
RUNTIME 在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)注:為RUNTIME時(shí)可以被反射機(jī)制所讀取
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
//參數(shù)默認(rèn)為空
? ? String value() default "";
}
在一般情況下我們使用RUNTIME即可。這樣在程序運(yùn)行時(shí)我們也可以通過反射機(jī)制來讀取到該注解。
@Document
@Inherited
上面?zhèn)z個(gè)注解我們使用的就不算很多了,大家有興趣可以自行百度一下,與生成文檔樹有關(guān)好像。
我們一會(huì)可以通過反射機(jī)制讀取到我們的注解。
二、反射(Reflect)
1.什么是反射?
反射指的是我們可以在運(yùn)行期間加載、探知、使用編譯期間完全未知的類。是一個(gè)動(dòng)態(tài)的機(jī)制,允許我們通過字符串來指揮程序?qū)嵗僮鲗傩?、調(diào)用方法。使得代碼提高了靈活性,但是同時(shí)也帶來了更多的資源開銷。
加載完類之后,在堆內(nèi)存中,就產(chǎn)生了一個(gè) Class 類型的對(duì)象(一個(gè) 類只有一個(gè) Class 對(duì)象),這個(gè)對(duì)象就包含了完整的類的結(jié)構(gòu)信息。 我們可以通過這個(gè)對(duì)象看到類的結(jié)構(gòu)。這個(gè)對(duì)象就像一面鏡子,透過 這個(gè)鏡子看到類的結(jié)構(gòu),所以,我們形象的稱之為:反射。
2.class類
我們?cè)谑褂梅瓷鋾r(shí),需要先獲得我們需要的類,而java.lang.Class這個(gè)類必不可少,他十分特殊,用來表示java中類型 (class/interface/enum/annotation/primitive type/void)本身。
Class類的對(duì)象包含了某個(gè)被加載類的結(jié)構(gòu)。一個(gè)被加載的類對(duì)應(yīng)一個(gè) Class對(duì)象。
當(dāng)一個(gè)class被加載,或當(dāng)加載器(class loader)的defineClass()被 JVM調(diào)用,JVM 便自動(dòng)產(chǎn)生一個(gè)Class 對(duì)象。
我們應(yīng)該如何獲取Class類的對(duì)象?
我們先創(chuàng)建一個(gè)普通的實(shí)體類。
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對(duì)象,參數(shù)為需要獲取類對(duì)象的全類名
? ? ? ? ? Class aClass = Class.forName("sml.reflect.User");
? ? ? ? //因?yàn)槭莿?dòng)態(tài)編譯,所有我們需要拋出類未找到的異常?
? ? ? ? } catch (ClassNotFoundException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
通過getClass()獲取
public class TestReflect {
? ? public static void main(String[] args) {
? ? ? ? //new一個(gè)user對(duì)象
? ? ? ? User user = new User();
? ? ? ? //通過user對(duì)象來獲取User類對(duì)象
? ? ? ? Class aClass = user.getClass();
? ? }
}
通過.class獲取
public class TestReflect {
? ? public static void main(String[] args) {
? ? ? ? //通過導(dǎo)包獲取類名點(diǎn)class來獲取類對(duì)象
? ? ? ? Class aClass = User.class;
? ? }
}
3.反射的基本操作
我們獲取到Class對(duì)象后,可以獲取該類的某些信息。在這里我們來看一些常用的。
1)獲取類名
Class aClass = Class.forName("sml.reflect.User");
//獲取全類名
String name = aClass.getName();
//獲取簡(jiǎn)單的類名
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");
注意:我們仔細(xì)看注釋,不帶Declared的方法職能獲取到public字段,且包括父類的,帶Declared的方法可以獲取到所有的自定義的字段!
測(cè)試一下:
3)獲取類的方法
Class aClass = Class.forName("sml.reflect.User");
//獲取該類的所有public方法,包括父類的
Method[] methods = aClass.getMethods();
//根據(jù)方法名獲取該類的public方法
Method method = aClass.getMethod("getName");
//如果該類為重寫方法,可以在第二個(gè)參數(shù)加上重寫方法的參數(shù)類型,不寫為無參數(shù)的方法
Method paramMethod = aClass.getMethod("getName",String.class)
//獲取該類的所有方法,不包括父類(僅自定義)
Method[] declaredMethods = aClass.getDeclaredMethods();
//根據(jù)方法名獲取該類的方法
Method declaredMethod = aClass.getDeclaredMethod("getName");
注:獲取方法的方式與獲取字段的方法一樣,在這里我們需要注意的是重寫的方法,一個(gè)類中存在倆個(gè)或多個(gè)方法名是一樣的,因此在根據(jù)方法名獲取方法時(shí),提供第二個(gè)參數(shù),為可變參數(shù)。參數(shù)類型為我們獲取方法的參數(shù)類型的類,如果不寫默認(rèn)為無參方法。
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ù)有幾個(gè)時(shí),就可以寫成可變參數(shù),我們?cè)谑褂脮r(shí)可以傳多個(gè)參數(shù)。注意我們寫的參數(shù)都為類對(duì)象!
我們獲取到構(gòu)造器第一想法應(yīng)該是實(shí)例化對(duì)象。
5)類的實(shí)例化
Class aClass = Class.forName("sml.reflect.User");
//通過class類直接實(shí)例化,使用的是User類的無參構(gòu)造器
User user = (User) aClass.newInstance();
//獲取構(gòu)造器來進(jìn)行實(shí)例化,這里獲取有參構(gòu)造器
Constructor declaredConstructor = aClass.getDeclaredConstructor(String.class, int.class);
//根據(jù)構(gòu)造器進(jìn)行實(shí)例化
User user1 = (User) declaredConstructor.newInstance("sml",18);
注:我們?cè)谑褂妙悓?duì)象直接實(shí)例化時(shí),一定要確保需實(shí)例化的類中存在無參構(gòu)造器,否則會(huì)報(bào)錯(cuò)。默認(rèn)獲取的是Object類型,因此最后需要進(jìn)行下類型轉(zhuǎn)化。
測(cè)試:我們?cè)赨ser類中重寫toString方法打印下獲取的對(duì)象看一下
我們獲取到對(duì)象是不是該通過獲取的對(duì)象調(diào)用方法,NO!我們通過反射來調(diào)用對(duì)象的方法。
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方法有倆個(gè)參數(shù)
//第一個(gè)是調(diào)用底層方法的對(duì)象,也就是通過哪個(gè)對(duì)象來調(diào)用方法
//第二個(gè)為可變參數(shù),是用于方法調(diào)用的參數(shù)
setName.invoke(user,"sml");
測(cè)試:我們打印下該對(duì)象看一下方法執(zhí)行了沒有
注:如果我們調(diào)用的方法為私有方法,雖然編譯器通過,在運(yùn)行時(shí)會(huì)報(bào)錯(cuò)的(java.lang.IllegalAccessException),這是因?yàn)閖ava的安全檢查。我們可以使用setAccessible(true)這個(gè)方法來跳過安全檢查。
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");
我們?cè)趯懗绦驎r(shí)一般通過getset方法來操作字段,下面我們同樣也是通過反射來操作字段。
7)字段的操作
Class aClass = Class.forName("sml.reflect.User");
User user = (User) aClass.newInstance();
//獲取name字段
Field name = aClass.getDeclaredField("name");
//通過該字段的set方法來改變?cè)撟侄蔚闹?,該字段有倆個(gè)參數(shù)
//第一個(gè)為應(yīng)該修改其字段的參數(shù)
//第二個(gè)為被修改字段的新值
name.set(user,"sml");
測(cè)試:我們打印下該對(duì)象,看一下name字段改變沒有
呀,報(bào)錯(cuò)了??催@個(gè)錯(cuò)眼熟不,我們?cè)谏厦嬲f過,這是因?yàn)槲覀兊膎ame字段為私有,我們不能直接去操作該字段,需要跳過安全檢查,我們加上name.setAccessible(true);再來運(yùn)行一下。
這還不夠,反射很強(qiáng),我們說過反射的對(duì)象像一面鏡子,我們能看到的東西都可以獲取,我們下面來讀取一下參數(shù)泛型,返回值泛型!
8)泛型的操作(Generic)
對(duì)于泛型我們應(yīng)該不會(huì)陌生,java采用泛型擦除的機(jī)制來引入泛型。也就是說java的泛型僅僅是給編譯器javac使用的,確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換的麻煩。但是一旦編譯完成,所有和泛型有關(guān)的數(shù)據(jù)全部擦除。
為了通過反射操作這些類型以迎合實(shí)際開發(fā)的需要,Java就新增了ParameterizedType, GenericArrayType,TypeVariable 和WildcardType幾種類型來代表不能被歸一到Class 類中的類型但是又和原始類型齊名的類型,這四種類型實(shí)現(xiàn)了Type接口。
類型 含義
ParameterizedType 參數(shù)化類型,帶有類型參數(shù)的類型,即常說的泛型,如:List《T》
TypeVariable 類型變量,如參數(shù)化類型Map《E,Y》中的Y、K等類型變量,表示泛指任何類
GenericArrayType (泛型)數(shù)組類型,比如List《T》[],T[]這種。注意,這不是我們說的一般數(shù)組,而是表示一種【元素類型是參數(shù)化類型或者類型變量的】數(shù)組類型
WildcardType 代表通配符表達(dá)式,或泛型表達(dá)式,比如【?】【? super T】【? extends T】。雖然WildcardType是Type的一個(gè)子接口,但并不是Java類型中的一種
我們演示一下反射讀取ParameterizedType類型。
public class TestReflect {
? ? //測(cè)試方法,返回類型與參數(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");
? ? ? ? ? ? //獲取到測(cè)試方法
? ? ? ? ? ? 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);
? ? ? ? ? ? ? ? //我們?cè)谘h(huán)時(shí)判斷該參數(shù)類型,若該參數(shù)屬于參數(shù)化類型
? ? ? ? ? ? ? ? if(genericParameterType instanceof ParameterizedType){
? ? ? ? ? ? ? ? ? ? //若屬于參數(shù)化類型,則獲取類型對(duì)象的數(shù)組,表示此類型的實(shí)際類型參數(shù)
? ? ? ? ? ? ? ? ? ? Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
? ? ? ? ? ? ? ? ? ? for (Type actualTypeArgument : actualTypeArguments) {
? ? ? ? ? ? ? ? ? ? ? ? //打印下實(shí)際類型參數(shù)
? ? ? ? ? ? ? ? ? ? ? ? System.out.println(actualTypeArgument);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
我們看著可能會(huì)很復(fù)雜,我們來分析一下。
首先,genericityTest這個(gè)為我們獲取的方法,我們通過genericityTest來獲取該方法的參數(shù),注意看,這里返回的是Type類型,也就是所有類型的父接口,我們打印下看,就是返回的List與User,也就是他的倆個(gè)參數(shù)List《User》,User。
接下來我們遍歷下類型Type參數(shù)數(shù)組,我們現(xiàn)在需要的是讀取參數(shù)化類型,那么我們對(duì)每一個(gè)Type參數(shù)進(jìn)行判斷,如果該Type參數(shù)屬于ParameterizedType參數(shù)化類型,那么我們?cè)讷@取到該泛型的實(shí)際類型參數(shù),也就是List中的User,注意,只有List《User》才屬于參數(shù)化類型,可以查看上面的表。
注:我們?cè)谏厦嫜菔镜闹皇谦@取方法的參數(shù),那么我們?nèi)绾潍@取返回值的類型?下面第二個(gè)方法
//獲取方法所有的參數(shù)類型
Type[] genericParameterTypes = genericityTest.getGenericParameterTypes();
//獲取返回值的參數(shù)類型,返回值只有一個(gè),所有不是數(shù)組
Type genericReturnType = genericityTest.getGenericReturnType();
9)注解的操作
注解的操作相對(duì)就比較簡(jiǎn)單了,如果我們想讀取類上、方法上或字段上的注解,我們僅需要獲取到你需要讀取的注解所修辭的類、方法或字段來獲取就可以。
我們以獲取方法上的注解來測(cè)試一下:
這里我們還是使用我們?cè)谖恼率撞縿?chuàng)建的TestAnnotation注解,只能放在方法上,保留到運(yùn)行時(shí)。
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();
? ? ? ? }
? ? }
}
注:如果我們需要獲取類上的注解,只需要獲取到類對(duì)象,然后.getDeclaredAnnotation()即可,其實(shí)不管是獲取類上的注解還是字段上的注解都是一樣的方法,關(guān)于有無Declared的方法,看到這里大家應(yīng)該也有了解了。
最后說一下,到這里我們可能沒有體會(huì)到注解的太大作用,在后面的文章我會(huì)寫一篇手寫SpringMVC框架的博客,當(dāng)然只是簡(jiǎn)單到不能簡(jiǎn)單的版本。如果將這篇文章看會(huì),在自己寫一下,關(guān)于注解與反射我相信大家應(yīng)該都會(huì)的差不多了。
————————————————
版權(quán)聲明:本文為CSDN博主「世代農(nóng)民」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_45056780/article/details/105127722