java注解與反射的基本使用(這一篇就夠了!)

一、注解(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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容