小白都能學(xué)會的Java注解與反射機(jī)制
目錄
前言
什么是注解
內(nèi)置注解
自定義注解
Java8 注解
Java反射機(jī)制
java.lang.Class 類
反射操作泛型
反射操作注解
性能分析
前言
Java注解和反射是很基礎(chǔ)的Java知識了,為何還要講它呢?因?yàn)槲以诿嬖噾?yīng)聘者的過程中,發(fā)現(xiàn)不少面試者很少使用過注解和反射,甚至有人只能說出@Override這一個(gè)注解。我建議大家還是盡量能在開發(fā)中使用注解和反射,有時(shí)候使用它們能讓你事半功倍,簡化代碼提高編碼的效率。很多優(yōu)秀的框架都基本使用了注解和反射,在Spring AOP中,就把注解和反射用得淋漓盡致。
什么是注解
Java注解(Annotation)亦叫Java標(biāo)注,是JDK5.0開始引入的一種注釋機(jī)制。 注解可以用在類、接口,方法、變量、參數(shù)以及包等之上。注解可以設(shè)置存在于不同的生命周期中,例如SOURCE(源碼中),CLASS(Class文件中,默認(rèn)是此保留級別),RUNTIME(運(yùn)行期中)。
注解以@注解名的形式存在于代碼中,Java中內(nèi)置了一些注解,例如@Override,當(dāng)然我們也可以自定義注解。注解也可以有參數(shù),例如@MyAnnotation(value = “陳皮”)。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
1
2
3
4
那注解有什么作用呢?其一是作為一種輔助信息,可以對程序做出一些解釋,例如@Override注解作用于方法上,表示此方法是重寫了父類的方法。其二,注解可以被其他程序讀取,例如編譯器,例如編譯器會對被@Override注解的方法檢測判斷方法名和參數(shù)等是否與父類相同,否則會編譯報(bào)錯(cuò);而且在運(yùn)行期可以通過反射機(jī)制訪問某些注解信息。
內(nèi)置注解
Java中有10個(gè)內(nèi)置注解,其中6個(gè)注解是作用在代碼上的,4個(gè)注解是負(fù)責(zé)注解其他注解的(即元注解),元注解提供對其他注解的類型說明。
自定義注解
使用@interface關(guān)鍵字自定義注解,其實(shí)底層就是定義了一個(gè)接口,而且自動繼承java.lang.annotation.Annotation接口。
我們自定義一個(gè)注解如下:
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String value(); }
1
2
3
4
5
我們使用命令javap反編譯我們定義的MyAnnotation注解的class文件,結(jié)果顯示如下。雖然注解隱式繼承了Annotation接口,但是Java不允許我們顯示通過extends關(guān)鍵字繼承Annotation接口甚至其他接口,否則編譯報(bào)錯(cuò)。
D:\>javap MyAnnotation.class Compiled from "MyAnnotation.java" public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation { public abstract java.lang.String value(); }
1
2
3
4
5
注解的定義內(nèi)容如下:
格式為public @interface 注解名 {定義內(nèi)容}
內(nèi)部的每一個(gè)方法實(shí)際是聲明了一個(gè)參數(shù),方法的名稱就是參數(shù)的名稱。
返回值類型就是參數(shù)的類型,而且返回值類型只能是基本類型(int,float,long,short,boolean,byte,double,char),Class,String,enum,Annotation以及上述類型的數(shù)組形式。
如果定義了參數(shù),可通過default關(guān)鍵字聲明參數(shù)的默認(rèn)值,若不指定默認(rèn)值,使用時(shí)就一定要顯示賦值,而且不允許使用null值,一般會使用空字符串或者0。
如果只有一個(gè)參數(shù),一般參數(shù)名為value,因?yàn)槭褂米⒔鈺r(shí),賦值可以不顯示寫出參數(shù)名,直接寫參數(shù)值。
import java.lang.annotation.*; /** * @Description 自定義注解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ @Target(ElementType.METHOD) // 此注解只能用在方法上。 @Retention(RetentionPolicy.RUNTIME) // 此注解保存在運(yùn)行時(shí)期,可以通過反射訪問。 @Inherited // 說明子類可以繼承此類的此注解。 @Documented // 此注解包含在用戶文檔中。 public @interface CustomAnnotation { String value(); // 使用時(shí)需要顯示賦值 int id() default 0; // 有默認(rèn)值,使用時(shí)可以不賦值 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/** * @Description 測試注解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ public class TestAnnotation { // @CustomAnnotation(value = "test") 只能注解在方法上,這里會報(bào)錯(cuò) private String str = "Hello World!"; @CustomAnnotation(value = "test") public static void main(String[] args) { System.out.println(str); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java8 注解
在這里講解下Java8之后的幾個(gè)注解和新特性,其中一個(gè)注解是@FunctionalInterface,它作用在接口上,標(biāo)識是一個(gè)函數(shù)式接口,即只有有一個(gè)抽象方法,但是可以有默認(rèn)方法。
@FunctionalInterface public interface Callback
{ public R call(P param); }
1
2
3
4
5
還有一個(gè)注解是@Repeatable,它允許在同一個(gè)位置使用多個(gè)相同的注解,而在Java8之前是不允許的。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(OperTypes.class) public @interface OperType { String[] value(); }
1
2
3
4
5
6
// 可以理解@OperTypes注解作為接收同一個(gè)類型上重復(fù)@OperType注解的容器 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface OperTypes { OperType[] value(); }
1
2
3
4
5
6
@OperType("add") @OperType("update") public class MyClass { }
1
2
3
4
5
注意,對于重復(fù)注解,不能再通過clz.getAnnotation(Class annotationClass)方法來獲取重復(fù)注解,Java8之后,提供了新的方法來獲取重復(fù)注解,即clz.getAnnotationsByType(Class annotationClass)方法。
package com.nobody; import java.lang.annotation.Annotation; /** * @Description * @Author Mr.nobody * @Date 2021/3/31 * @Version 1.0 */ @OperType("add") @OperType("update") public class MyClass { public static void main(String[] args) { Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在Java8中,ElementType枚舉新增了兩個(gè)枚舉成員,分別為TYPE_PARAMETER和TYPE_USE,TYPE_PARAMETER標(biāo)識注解可以作用于類型參數(shù),TYPE_USE標(biāo)識注解可以作用于標(biāo)注任意類型(除了Class)。
Java反射機(jī)制
我們先了解下什么是靜態(tài)語言和動態(tài)語言。動態(tài)語言是指在運(yùn)行時(shí)可以改變其自身結(jié)構(gòu)的語言。例如新的函數(shù),對象,甚至代碼可以被引進(jìn),已有的函數(shù)可以被刪除或者結(jié)構(gòu)上的一些變化。簡單說即是在運(yùn)行時(shí)代碼可以根據(jù)某些條件改變自身結(jié)構(gòu)。動態(tài)語言主要有C#,Object-C,JavaScript,PHP,Python等。靜態(tài)語言是指運(yùn)行時(shí)結(jié)構(gòu)不可改變的語言,例如Java,C,C++等。
Java不是動態(tài)語言,但是它可以稱為準(zhǔn)動態(tài)語言,因?yàn)镴ava可以利用反射機(jī)制獲得類似動態(tài)語言的特性,Java的動態(tài)性讓它在編程時(shí)更加靈活。
反射機(jī)制允許程序在執(zhí)行期借助于Reflection API取得任何類的內(nèi)部信息,并能直接操作任意對象的內(nèi)部屬性以及方法等。類在被加載完之后,會在堆內(nèi)存的方法區(qū)中生成一個(gè)Class類型的對象,一個(gè)類只有一個(gè)Class對象,這個(gè)對象包含了類的結(jié)構(gòu)信息。我們可以通過這個(gè)對象看到類的結(jié)構(gòu)。
比如我們可以通過Class clz = Class.forName("java.lang.String");獲得String類的Class對象。我們知道每個(gè)類都隱式繼承Object類,Object類有個(gè)getClass()方法也能獲取Class對象。
Java反射機(jī)制提供的功能
在運(yùn)行時(shí)判斷任意一個(gè)對象所屬的類
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對象
在運(yùn)行時(shí)判斷任意一個(gè)類具有的成員變量和方法
在運(yùn)行時(shí)獲取泛型信息
在運(yùn)行時(shí)調(diào)用任意一個(gè)對象的成員變量和方法
在運(yùn)行時(shí)獲取注解
生成動態(tài)代理
…
Java反射機(jī)制的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):實(shí)現(xiàn)動態(tài)創(chuàng)建對象和編譯,有更加的靈活性。
缺點(diǎn):對性能有影響。使用反射其實(shí)是一種解釋操作,即告訴JVM我們想要做什么,然后它滿足我們的要求,所以總是慢于直接執(zhí)行相同的操作。
Java反射相關(guān)的主要API
java.lang.Class:代表一個(gè)類
java.lang.reflect.Method:代表類的方法
java.lang.reflect.Field:代表類的成員變量
java.lang.reflect.Constructor:代表類的構(gòu)造器
我們知道在運(yùn)行時(shí)通過反射可以準(zhǔn)確獲取到注解信息,其實(shí)以上類(Class,Method,F(xiàn)ield,Constructor等)都直接或間接實(shí)現(xiàn)了AnnotatedElement接口,并實(shí)現(xiàn)了它定義的方法,AnnotatedElement接口的作用主要用于表示正在JVM中運(yùn)行的程序中已使用注解的元素,通過該接口提供的方法可以獲取到注解信息。
java.lang.Class 類
在Java反射中,最重要的是Class這個(gè)類了。Class本身也是一個(gè)類。當(dāng)程序想要使用某個(gè)類時(shí),如果此類還未被加載到內(nèi)存中,首先會將類的class文件字節(jié)碼加載到內(nèi)存中,并將這些靜態(tài)數(shù)據(jù)轉(zhuǎn)換為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu),然后生成一個(gè)Class類型的對象(Class對象只能由系統(tǒng)創(chuàng)建),一個(gè)類只有一個(gè)Class對象,這個(gè)對象包含了類的結(jié)構(gòu)信息。我們可以通過這個(gè)對象看到類的結(jié)構(gòu)。每個(gè)類的實(shí)例都會記得自己是由哪個(gè)Class實(shí)例所生成的。
通過Class對象可以知道某個(gè)類的屬性,方法,構(gòu)造器,注解,以及實(shí)現(xiàn)了哪些接口等信息。注意,只有class,interface,enum,annotation,primitive type,void,[] 等才有Class對象。
package com.nobody; import java.lang.annotation.ElementType; import java.util.Map; public class TestClass { public static void main(String[] args) { // 類 Class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
獲取Class對象的方法
如果知道具體的類,可通過類的class屬性獲取,這種方法最安全可靠并且性能最高。Class clz = User.class;
通過類的實(shí)例的getClass()方法獲取。Class clz = user.getClass();
如果知道一個(gè)類的全限定類名,并且在類路徑下,可通過Class.forName()方法獲取,但是可能會拋出ClassNotFoundException。Class clz = Class.forName("com.nobody.User");
內(nèi)置的基本數(shù)據(jù)類型可以直接通過類名.Type獲取。Class
通過類加載器ClassLoader獲取
Class類的常用方法
public static Class> forName(String className):創(chuàng)建一個(gè)指定全限定類名的Class對象
public T newInstance():調(diào)用Class對象所代表的類的無參構(gòu)造方法,創(chuàng)建一個(gè)實(shí)例
public String getName():返回Class對象所代表的類的全限定名稱。
public String getSimpleName():返回Class對象所代表的類的簡單名稱。
public native Class super T> getSuperclass():返回Class對象所代表的類的父類的Class對象,這是一個(gè)本地方法
public Class>[] getInterfaces():返回Class對象的接口
public Field[] getFields():返回Class對象所代表的實(shí)體的public屬性Field對象數(shù)組
public Field[] getDeclaredFields():返回Class對象所代表的實(shí)體的所有屬性Field對象數(shù)組
public Field getDeclaredField(String name):獲取指定屬性名的Field對象
public Method[] getDeclaredMethods():返回Class對象所代表的實(shí)體的所有Method對象數(shù)組
public Method getDeclaredMethod(String name, Class>… parameterTypes):返回指定名稱和參數(shù)類型的Method對象
myClassClass.getDeclaredConstructors();:返回所有Constructor對象的數(shù)組
public ClassLoader getClassLoader():返回當(dāng)前類的類加載器
在反射中經(jīng)常會使用到Method的invoke方法,即public Object invoke(Object obj, Object... args),我們簡單說明下:
第一個(gè)Object對應(yīng)原方法的返回值,若原方法沒有返回值,則返回null。
第二個(gè)Object對象對應(yīng)調(diào)用方法的實(shí)例,若原方法為靜態(tài)方法,則參數(shù)obj可為null。
第二個(gè)Object對應(yīng)若原方法形參列表,若參數(shù)為空,則參數(shù)args為null。
若原方法聲明為private修飾,則調(diào)用invoke方法前,需要顯示調(diào)用方法對象的method.setAccessible(true)方法,才可訪問private方法。
反射操作泛型
泛型是JDK 1.5的一項(xiàng)新特性,它的本質(zhì)是參數(shù)化類型(Parameterized Type)的應(yīng)用,也就是說所操作的數(shù)據(jù)類型被指定為一個(gè)參數(shù),在用到的時(shí)候再指定具體的類型。這種參數(shù)類型可以用在類、接口和方法的創(chuàng)建中,分別稱為泛型類、泛型接口和泛型方法。
在Java中,采用泛型擦除的機(jī)制來引入泛型,泛型能編譯器使用javac時(shí)確保數(shù)據(jù)的安全性和免去強(qiáng)制類型轉(zhuǎn)換問題,泛型提供了編譯時(shí)類型安全檢測機(jī)制,該機(jī)制允許程序員在編譯時(shí)檢測到非法的類型。并且一旦編譯完成,所有和泛型有關(guān)的類型會被全部擦除。
Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType等幾種類型,能讓我們通過反射操作這些類型。
ParameterizedType:表示一種參數(shù)化類型,比如Collection
GenericArrayType:表示種元素類型是參數(shù)化類型或者類型變量的數(shù)組類型
TypeVariable:是各種類型變量的公共父接口
WildcardType:代表種通配符類型表達(dá)式
package com.nobody; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; public class TestReflectGenerics { public Map
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
反射操作注解
在Java運(yùn)行時(shí),通過反射獲取代碼中的注解是比較常用的手段了,獲取到了注解之后,就能知道注解的所有信息了,然后根據(jù)信息進(jìn)行相應(yīng)的操作。下面通過一個(gè)例子,獲取類和屬性的注解,解析映射為數(shù)據(jù)庫中的表信息。
package com.nobody; import java.lang.annotation.*; public class AnalysisAnnotation { public static void main(String[] args) throws Exception { Class> aClass = Class.forName("com.nobody.Book"); // 獲取類的指定注解,并且獲取注解的值 Table annotation = aClass.getAnnotation(Table.class); String value = annotation.value(); System.out.println("Book類映射的數(shù)據(jù)庫表名:" + value); java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName"); TableField annotation1 = bookName.getAnnotation(TableField.class); System.out.println("bookName屬性映射的數(shù)據(jù)庫字段屬性 - 列名:" + annotation1.colName() + ",類型:" + annotation1.type() + ",長度:" + annotation1.length()); java.lang.reflect.Field price = aClass.getDeclaredField("price"); TableField annotation2 = price.getAnnotation(TableField.class); System.out.println("price屬性映射的數(shù)據(jù)庫字段屬性 - 列名:" + annotation2.colName() + ",類型:" + annotation2.type() + ",長度:" + annotation2.length()); } } // 作用于類的注解,用于解析表數(shù)據(jù) @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Table { // 表名 String value(); } // 作用于字段,用于解析表列 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface TableField { // 列名 String colName(); // 列類型 String type(); // 長度 int length(); } @Table("t_book") class Book { @TableField(colName = "name", type = "varchar", length = 15) String bookName; @TableField(colName = "price", type = "int", length = 10) int price; } // 輸出結(jié)果 Book類映射的數(shù)據(jù)庫表名:t_book bookName屬性映射的數(shù)據(jù)庫字段屬性 - 列名:name,類型:varchar,長度:15 price屬性映射的數(shù)據(jù)庫字段屬性 - 列名:price,類型:int,長度:10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
性能分析
前面我們說過,反射對性能有一定影響。因?yàn)榉瓷涫且环N解釋操作,它總是慢于直接執(zhí)行相同的操作。而且Method,F(xiàn)ield,Constructor都有setAccessible()方法,它的作用是開啟或禁用訪問安全檢查。如果我們程序代碼中用到了反射,而且此代碼被頻繁調(diào)用,為了提高反射效率,則最好禁用訪問安全檢查,即設(shè)置為true。
package com.nobody; import java.lang.reflect.Method; public class TestReflectSpeed { // 10億次 private static int times = 1000000000; public static void main(String[] args) throws Exception { test01(); test02(); test03(); } public static void test01() { Teacher t = new Teacher(); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { t.getName(); } long end = System.currentTimeMillis(); System.out.println("普通方式執(zhí)行10億次消耗:" + (end - start) + "ms"); } public static void test02() throws Exception { Teacher teacher = new Teacher(); Class> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("反射方式執(zhí)行10億次消耗:" + (end - start) + "ms"); } public static void test03() throws Exception { Teacher teacher = new Teacher(); Class> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); getName.setAccessible(true); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("關(guān)閉安全檢查反射方式執(zhí)行10億次消耗:" + (end - start) + "ms"); } } class Teacher { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } //輸出結(jié)果 普通方式執(zhí)行10億次消耗:13ms 反射方式執(zhí)行10億次消耗:20141ms 關(guān)閉安全檢查反射方式執(zhí)行10億次消耗:8233ms
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
通過實(shí)驗(yàn)可知,反射比直接執(zhí)行相同的方法慢了很多,特別是當(dāng)反射的操作被頻繁調(diào)用時(shí)效果更明顯,當(dāng)然通過關(guān)閉安全檢查可以提高一些速度。所以,放射也不應(yīng)該泛濫成災(zāi)的,而是適度使用才能發(fā)揮最大作用。
Java 數(shù)據(jù)結(jié)構(gòu)
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。
版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。