【轉】JAVA反射與註解

轉載自:https://www.daidingkang.cc/2017/07/18/java-reflection-annotations/

前言

如今在咱們構建本身或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,好比:Butter KnifeRetrofit 2Dagger 2GreenDao等,若是你沒用過,那你須要找時間補一下啦;有時在使用後咱們會好奇他們究竟是怎麼作到這種簡潔、高效、鬆耦合等諸多優勢的,固然這裏我不探討它們具體怎麼實現的 (能夠看看我以前寫的幾篇文章) ,而關心的是它們都用到一樣的技術那就是本篇所講的反射註解,並實現的依賴注入。html

閱讀本篇文章有助於你更好的理解這些大形框架的原理和複習Java的知識點。爲何要把反射放在前面講呢,其實是由於咱們學習註解的時候須要用到反射機制,因此,先學習反射有助於理解後面的知識。java

JAVA反射

主要是指程序能夠訪問,檢測和修改它自己狀態或行爲的一種能力,並能根據自身行爲的狀態和結果,調整或修改應用所描述行爲的狀態和相關的語義。面試

反射機制是什麼

面試有可能會問到,這句話無論你能不能理解,可是你只要記住就能夠了數組

反射機制就是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。app

用一句話總結就是反射能夠實如今運行時能夠知道任意一個類屬性和方法框架

反射機制能作什麼

反射機制主要提供瞭如下功能:ide

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具備的成員變量和方法;
  • 在運行時調用任意一個對象的方法;
  • 生成動態代理(ps:這個知識點也很重要,後續會爲你們講到)

Java 反射機制的應用場景

  • 逆向代碼 ,例如反編譯
  • 與註解相結合的框架 例如Retrofit
  • 單純的反射機制應用框架 例如EventBus
  • 動態生成類框架 例如Gson

反射機制的優勢與缺點

爲何要用反射機制?直接建立對象不就能夠了嗎,這就涉及到了動態與靜態的概念函數

  • 靜態編譯:在編譯時肯定類型,綁定對象,即經過。工具

  • 動態編譯:運行時肯定類型,綁定對象。動態編譯最大限度發揮了java的靈活性,體現了多態的應用,有以下降類之間的藕合性。post

優勢

  • 能夠實現動態建立對象和編譯,體現出很大的靈活性,特別是在J2EE的開發中它的靈活性就表現的十分明顯。好比,一個大型的軟件,不可能一次就把把它設計的很完美,當這個程序編譯後,發佈了,當發現須要更新某些功能時,咱們不可能要用戶把之前的卸載,再從新安裝新的版本,假如這樣的話,這個軟件確定是沒有多少人用的。採用靜態的話,須要把整個程序從新編譯一次才能夠實現功能的更新,而採用反射機制的話,它就能夠不用卸載,只須要在運行時才動態的建立和編譯,就能夠實現該功能。

缺點

  • 對性能有影響。使用反射基本上是一種解釋操做,咱們能夠告訴JVM,咱們但願作什麼而且它知足咱們的要求。這類操做老是慢於只直接執行相同的操做。

理解Class類和類類型

想要了解反射首先理解一下Class類,它是反射實現的基礎。
類是java.lang.Class類的實例對象,而Class是全部類的類(There is a class named Class)
對於普通的對象,咱們通常都會這樣建立和表示:

1
Code code1 = new Code();

上面說了,全部的類都是Class的對象,那麼如何表示呢,可不能夠經過以下方式呢:

1
Class c = new Class();

可是咱們查看Class的源碼時,是這樣寫的:

1
2
3
private Class(ClassLoader loader) { 
classLoader = loader;
}

能夠看到構造器是私有的,只有JVM能夠建立Class的對象,所以不能夠像普通類同樣new一個Class對象,雖然咱們不能new一個Class對象,可是卻能夠經過已有的類獲得一個Class對象,共有三種方式,以下:

1
2
3
Class c1 = Code.class; 這說明任何一個類都有一個隱含的靜態成員變量class,這種方式是經過獲取類的靜態成員變量class獲得的
Class c2 = code1.getClass(); code1是Code的一個對象,這種方式是經過一個類的對象的getClass()方法得到的
Class c3 = Class.forName("com.trigl.reflect.Code"); 這種方法是Class類調用forName方法,經過一個類的全量限定名得到

這裏,c一、c二、c3都是Class的對象,他們是徹底同樣的,並且有個學名,叫作Code的類類型(class type)。
這裏就讓人奇怪了,前面不是說Code是Class的對象嗎,而c一、c二、c3也是Class的對象,那麼Code和c一、c二、c3不就同樣了嗎?爲何還叫Code什麼類類型?這裏不要糾結於它們是否相同,只要理解類類型是幹什麼的就行了,顧名思義,類類型就是類的類型,也就是描述一個類是什麼,都有哪些東西,因此咱們能夠經過類類型知道一個類的屬性和方法,而且能夠調用一個類的屬性和方法,這就是反射的基礎。

舉個簡單例子代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//第一種:Class c1 = Code.class;
Class class1=ReflectDemo.class;
System.out.println(class1.getName());

//第二種:Class c2 = code1.getClass();
ReflectDemo demo2= new ReflectDemo();
Class c2 = demo2.getClass();
System.out.println(c2.getName());

//第三種:Class c3 = Class.forName("com.trigl.reflect.Code");
Class class3 = Class.forName("com.tengj.reflect.ReflectDemo");
System.out.println(class3.getName());
}
}

執行結果:

1
2
3
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo
com.tengj.reflect.ReflectDemo

Java反射相關操做

在這裏先看一下sun爲咱們提供了那些反射機制中的類:
java.lang.Class;
java.lang.reflect.Constructor; java.lang.reflect.Field;
java.lang.reflect.Method;
java.lang.reflect.Modifier;

前面咱們知道了怎麼獲取Class,那麼咱們能夠經過這個Class幹什麼呢?
總結以下:

  • 獲取成員方法Method
  • 獲取成員變量Field
  • 獲取構造函數Constructor

下面來具體介紹

  1. 獲取成員方法信息

    兩個參數分別是方法名和方法參數類的類類型列表。

1
2
3
4
5
6
7
8
public Method getDeclaredMethod(String name, Class<?>... parameterTypes) // 獲得該類全部的方法,不包括父類的 
public Method getMethod(String name, Class<?>... parameterTypes) // 獲得該類全部的public方法,包括父類的

//具體使用
Method[] methods = class1.getDeclaredMethods();//獲取class對象的全部聲明方法
Method[] allMethods = class1.getMethods();//獲取class對象的全部public方法 包括父類的方法
Method method = class1.getMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的public方法
Method declaredMethod = class1.getDeclaredMethod("info", String.class);//返回次Class對象對應類的、帶指定形參列表的方法

舉個例子:

例如類A有以下一個方法:

1
2
3
public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"歲");
}

如今知道A有一個對象a,那麼就能夠經過:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person"); //先生成class
Object o = c.newInstance(); //newInstance能夠初始化一個實例
Method method = c.getMethod("fun", String.class, int.class);//獲取方法
method.invoke(o, "tengj", 10); //經過invoke調用該方法,參數第一個爲實例對象,後面爲具體參數值

完整代碼以下:

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
public class Person {
private String name;
private int age;
private String msg="hello wrold";
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;
}

public Person() {
}

private Person(String name) {
this.name = name;
System.out.println(name);
}

public void fun() {
System.out.println("fun");
}

public void fun(String name,int age) {
System.out.println("我叫"+name+",今年"+age+"歲");
}
}

public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Object o = c.newInstance();
Method method = c.getMethod("fun", String.class, int.class);
method.invoke(o, "tengj", 10);
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

我叫tengj,今年10歲

怎樣,是否是感受很厲害,咱們只要知道這個類的路徑全稱就能玩弄它於鼓掌之間。

有時候咱們想獲取類中全部成員方法的信息,要怎麼辦。能夠經過如下幾步來實現:

1.獲取全部方法的數組:

1
2
3
4
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods(); // 獲得該類全部的方法,不包括父類的
或者:
Method[] methods = c.getMethods();// 獲得該類全部的public方法,包括父類的

2.而後循環這個數組就獲得每一個方法了:

1
for (Method method : methods)

完整代碼以下:
person類跟上面同樣,這裏以及後面就不貼出來了,只貼關鍵代碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Method[] methods = c.getDeclaredMethods();
for(Method m:methods){
String methodName= m.getName();
System.out.println(methodName);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

getName
setName
setAge
fun
fun
getAge

這裏若是把c.getDeclaredMethods();改爲c.getMethods();執行結果以下,多了不少方法,覺得把Object裏面的方法也打印出來了,由於Object是全部類的父類:

getName
setName
getAge
setAge
fun
fun
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll
  1. 獲取成員變量信息

想想成員變量中都包括什麼:成員變量類型+成員變量名

類的成員變量也是一個對象,它是java.lang.reflect.Field的一個對象,因此咱們經過java.lang.reflect.Field裏面封裝的方法來獲取這些信息。

單獨獲取某個成員變量,經過Class類的如下方法實現:

參數是成員變量的名字

1
2
3
4
5
6
7
8
public Field getDeclaredField(String name) // 得到該類自身聲明的全部變量,不包括其父類的變量
public Field getField(String name) // 得到該類自全部的public成員變量,包括其父類變量

//具體實現
Field[] allFields = class1.getDeclaredFields();//獲取class對象的全部屬性
Field[] publicFields = class1.getFields();//獲取class對象的public屬性
Field ageField = class1.getDeclaredField("age");//獲取class指定屬性
Field desField = class1.getField("des");//獲取class指定的public屬性

舉個例子:

例如一個類A有以下成員變量:

1
private int n;

若是A有一個對象a,那麼就能夠這樣獲得其成員變量:

1
2
Class c = a.getClass();
Field field = c.getDeclaredField("n");

完整代碼以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//獲取成員變量
Field field = c.getDeclaredField("msg"); //由於msg變量是private的,因此不能用getField方法
Object o = c.newInstance();
field.setAccessible(true);//設置是否容許訪問,由於該變量是private的,因此要手動設置容許訪問,若是msg是public的就不須要這行了。
Object msg = field.get(o);
System.out.println(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

hello wrold

一樣,若是想要獲取全部成員變量的信息,能夠經過如下幾步

1.獲取全部成員變量的數組:

1
Field[] fields = c.getDeclaredFields();

2.遍歷變量數組,得到某個成員變量field

1
for (Field field : fields)

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
Field[] fields = c.getDeclaredFields();
for(Field field :fields){
System.out.println(field.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

name
age
msg
  1. 獲取構造函數

最後再想想構造函數中都包括什麼:構造函數參數
同上,類的成構造函數也是一個對象,它是java.lang.reflect.Constructor的一個對象,因此咱們經過java.lang.reflect.Constructor裏面封裝的方法來獲取這些信息。

單獨獲取某個構造函數,經過Class類的如下方法實現:

這個參數爲構造函數參數類的類類型列表

1
2
3
4
5
6
7
8
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 得到該類全部的構造器,不包括其父類的構造器
public Constructor<T> getConstructor(Class<?>... parameterTypes) // 得到該類因此public構造器,包括父類

//具體
Constructor<?>[] allConstructors = class1.getDeclaredConstructors();//獲取class對象的全部聲明構造函數
Constructor<?>[] publicConstructors = class1.getConstructors();//獲取class對象public構造函數
Constructor<?> constructor = class1.getDeclaredConstructor(String.class);//獲取指定聲明構造函數
Constructor publicConstructor = class1.getConstructor(String.class);//獲取指定聲明的public構造函數

舉個例子:

例如類A有以下一個構造函數:

1
2
3
public A(String a, int b) {
// code body
}

那麼就能夠經過:

1
Constructor constructor = a.getDeclaredConstructor(String.class, int.class);

來獲取這個構造函數。

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ReflectDemo {
public static void main(String[] args){
try {
Class c = Class.forName("com.tengj.reflect.Person");
//獲取構造函數
Constructor constructor = c.getDeclaredConstructor(String.class);
constructor.setAccessible(true);//設置是否容許訪問,由於該構造器是private的,因此要手動設置容許訪問,若是構造器是public的就不須要這行了。
constructor.newInstance("tengj");
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

tengj

注意:Class的newInstance方法,只能建立只包含無參數的構造函數的類,若是某類只有帶參數的構造函數,那麼就要使用另一種方式:

1
fromClass.getDeclaredConstructor(String.class).newInstance("tengj");

獲取全部的構造函數,能夠經過如下步驟實現:

1.獲取該類的全部構造函數,放在一個數組中:

1
Constructor[] constructors = c.getDeclaredConstructors();

2.遍歷構造函數數組,得到某個構造函數constructor:

1
for (Constructor constructor : constructors)

完整代碼:

1
2
3
4
5
6
7
8
9
10
11
public class ReflectDemo {
public static void main(String[] args){
Constructor[] constructors = c.getDeclaredConstructors();
for(Constructor constructor:constructors){
System.out.println(constructor);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

執行結果:

public com.tengj.reflect.Person()
public com.tengj.reflect.Person(java.lang.String)
  1. 其餘方法

註解須要用到的

1
2
3
4
Annotation[] annotations = (Annotation[]) class1.getAnnotations();//獲取class對象的全部註解 
Annotation annotation = (Annotation) class1.getAnnotation(Deprecated.class);//獲取class對象指定註解
Type genericSuperclass = class1.getGenericSuperclass();//獲取class對象的直接超類的
Type Type[] interfaceTypes = class1.getGenericInterfaces();//獲取class對象的全部接口的type集合

獲取class對象的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
boolean isPrimitive = class1.isPrimitive();//判斷是不是基礎類型 
boolean isArray = class1.isArray();//判斷是不是集合類
boolean isAnnotation = class1.isAnnotation();//判斷是不是註解類
boolean isInterface = class1.isInterface();//判斷是不是接口類
boolean isEnum = class1.isEnum();//判斷是不是枚舉類
boolean isAnonymousClass = class1.isAnonymousClass();//判斷是不是匿名內部類
boolean isAnnotationPresent = class1.isAnnotationPresent(Deprecated.class);//判斷是否被某個註解類修飾
String className = class1.getName();//獲取class名字 包含包名路徑
Package aPackage = class1.getPackage();//獲取class的包信息
String simpleName = class1.getSimpleName();//獲取class類名
int modifiers = class1.getModifiers();//獲取class訪問權限
Class<?>[] declaredClasses = class1.getDeclaredClasses();//內部類
Class<?> declaringClass = class1.getDeclaringClass();//外部類

getSuperclass():獲取某類的父類
getInterfaces():獲取某類實現的接口

經過反射了解集合泛型的本質

擴展的知識點,瞭解就能夠了。後續會爲你們寫一篇關於泛型的文章。

首先下結論:

Java中集合的泛型,是防止錯誤輸入的,只在編譯階段有效,繞過編譯到了運行期就無效了。

下面經過一個實例來驗證:

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
/**
* 集合泛型的本質
*/
public class GenericEssence {
public static void main(String[] args) {
List list1 = new ArrayList(); // 沒有泛型
List<String> list2 = new ArrayList<String>(); // 有泛型


/*
* 1.首先觀察正常添加元素方式,在編譯器檢查泛型,
* 這個時候若是list2添加int類型會報錯
*/
list2.add("hello");
// list2.add(20); // 報錯!list2有泛型限制,只能添加String,添加int報錯
System.out.println("list2的長度是:" + list2.size()); // 此時list2長度爲1


/*
* 2.而後經過反射添加元素方式,在運行期動態加載類,首先獲得list1和list2
* 的類類型相同,而後再經過方法反射繞過編譯器來調用add方法,看可否插入int
* 型的元素
*/
Class c1 = list1.getClass();
Class c2 = list2.getClass();
System.out.println(c1 == c2); // 結果:true,說明類類型徹底相同

// 驗證:咱們能夠經過方法的反射來給list2添加元素,這樣能夠繞過編譯檢查
try {
Method m = c2.getMethod("add", Object.class); // 經過方法反射獲得add方法
m.invoke(list2, 20); // 給list2添加一個int型的,上面顯示在編譯器是會報錯的
System.out.println("list2的長度是:" + list2.size()); // 結果:2,說明list2長度增長了,並無泛型檢查
} catch (Exception e) {
e.printStackTrace();
}

/*
* 綜上能夠看出,在編譯器的時候,泛型會限制集合內元素類型保持一致,可是編譯器結束進入
* 運行期之後,泛型就再也不起做用了,即便是不一樣類型的元素也能夠插入集合。
*/
}
}

執行結果:

list2的長度是:1
true
list2的長度是:2

思惟導圖

有助於理解上述所講的知識點

拓展閱讀
Java反射機制深刻詳解 - 火星十一郎 - 博客園
Java反射入門 - Trigl的博客 - CSDN博客
Java反射機制 - ①塊腹肌 - 博客園
Java 反射機制淺析 - 孤旅者 - 博客園
反射機制的理解及其用途 - 天天進步一點點! - ITeye博客
Java動態代理與反射詳解 - 浩大王 - 博客園

JAVA註解

概念及做用

  1. 概念
  • 註解即元數據,就是源代碼的元數據
  • 註解在代碼中添加信息提供了一種形式化的方法,能夠在後續中更方便的 使用這些數據
  • Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。它是一種由JSR-175標準選擇用來描述元數據的一種工具。
  1. 做用
  • 生成文檔
  • 跟蹤代碼依賴性,實現替代配置文件功能,減小配置。如Spring中的一些註解
  • 在編譯時進行格式檢查,如@Override等
  • 每當你建立描述符性質的類或者接口時,一旦其中包含重複性的工做,就能夠考慮使用註解來簡化與自動化該過程。

什麼是java註解?

在java語法中,使用@符號做爲開頭,並在@後面緊跟註解名。被運用於類,接口,方法和字段之上,例如:

1
2
3
4
@Override
void myMethod() {
......
}

這其中@Override就是註解。這個註解的做用也就是告訴編譯器,myMethod()方法覆寫了父類中的myMethod()方法。

java中內置的註解

java中有三個內置的註解:

  • @Override:表示當前的方法定義將覆蓋超類中的方法,若是出現錯誤,編譯器就會報錯。
  • @Deprecated:若是使用此註解,編譯器會出現警告信息。
  • @SuppressWarnings:忽略編譯器的警告信息。

本文不在闡述三種內置註解的使用情節和方法,感興趣的請看這裏

元註解

自定義註解的時候用到的,也就是自定義註解的註解;(這句話我本身說的,不知道對不對)

元註解的做用就是負責註解其餘註解。Java5.0定義了4個標準的meta-annotation類型,它們被用來提供對其它 annotation類型做說明。

Java5.0定義的4個元註解:

  1. @Target

  2. @Retention

  3. @Documented

  4. @Inherited

java8加了兩個新註解,後續我會講到。

這些類型和它們所支持的類在java.lang.annotation包中能夠找到。

@Target

@Target說明了Annotation所修飾的對象範圍:Annotation可被用於 packages、types(類、接口、枚舉、Annotation類型)、類型成員(方法、構造方法、成員變量、枚舉值)、方法參數和本地變量(如循環變量、catch參數)。在Annotation類型的聲明中使用了target可更加明晰其修飾的目標。

做用:用於描述註解的使用範圍(即:被描述的註解能夠用在什麼地方)

取值(ElementType)有:

類型 用途
CONSTRUCTOR 用於描述構造器
FIELD 用於描述域
LOCAL_VARIABLE 用於描述局部變量
METHOD 用於描述方法
PACKAGE 用於描述包
PARAMETER 用於描述參數
TYPE 用於描述類、接口(包括註解類型) 或enum聲明

好比說這個註解表示只能在方法中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.METHOD})
public @interface MyCustomAnnotation {

}

//使用
public class MyClass {
@MyCustomAnnotation
public void myMethod()
{
......
}
}

@Retention

@Retention定義了該Annotation被保留的時間長短:某些Annotation僅出如今源代碼中,而被編譯器丟棄;而另外一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會被虛擬機忽略,而另外一些在class被裝載時將被讀取(請注意並不影響class的執行,由於Annotation與class在使用上是被分離的)。使用這個meta-Annotation能夠對 Annotation的「生命週期」限制。

做用:表示須要在什麼級別保存該註釋信息,用於描述註解的生命週期(即:被描述的註解在什麼範圍內有效)

取值(RetentionPoicy)有:

類型 用途 說明
SOURCE 在源文件中有效(即源文件保留) 僅出如今源代碼中,而被編譯器丟棄
CLASS 在class文件中有效(即class保留) 被編譯在class文件中
RUNTIME 在運行時有效(即運行時保留) 編譯在class文件中

使用示例:

1
2
3
4
5
6
7
8
/***
* 字段註解接口
*/
@Target(value = {ElementType.FIELD})//註解能夠被添加在屬性上
@Retention(value = RetentionPolicy.RUNTIME)//註解保存在JVM運行時刻,可以在運行時刻經過反射API來獲取到註解的信息
public @interface Column {
String name();//註解的name屬性
}

@Documented

@Documented用於描述其它類型的annotation應該被做爲被標註的程序成員的公共API,所以能夠被例如javadoc此類的工具文檔化。Documented是一個標記註解,沒有成員。

做用:將註解包含在javadoc中

示例:

1
2
3
java.lang.annotation.Documented
@Documented
public @interface MyCustomAnnotation { //Annotation body}

@Inherited

  • 是一個標記註解
  • 闡述了某個被標註的類型是被繼承的
  • 使用了@Inherited修飾的annotation類型被用於一個class,則這個annotation將被用於該class的子類
    @Inherited annotation類型是被標註過的class的子類所繼承。類並不從實現的接口繼承annotation,方法不從它所重載的方法繼承annotation
  • 當@Inherited annotation類型標註的annotation的Retention是RetentionPolicy.RUNTIME,則反射API加強了這種繼承性。若是咱們使用java.lang.reflect去查詢一個@Inherited annotation類型的annotation時,反射代碼檢查將展開工做:檢查class和其父類,直到發現指定的annotation類型被發現,或者到達類繼承結構的頂層。

做用:容許子類繼承父類中的註解

示例,這裏的MyParentClass 使用的註解標註了@Inherited,因此子類能夠繼承這個註解信息:

1
2
3
4
java.lang.annotation.Inherited
@Inherited
public @interface MyCustomAnnotation {
}
1
2
3
4
@MyCustomAnnotation
public class MyParentClass {
...
}
1
2
3
public class MyChildClass extends MyParentClass { 
...
}

自定義註解

格式

1
2
3
public @interface 註解名{
定義體
}

註解參數的可支持數據類型:

  • 全部基本數據類型(int,float,double,boolean,byte,char,long,short)
  • String 類型
  • Class類型
  • enum類型
  • Annotation類型
  • 以上全部類型的數組

規則

  • 修飾符只能是public 或默認(default)
  • 參數成員只能用基本類型byte,short,int,long,float,double,boolean八種基本類型和String,Enum,Class,annotations及這些類型的數組
  • 若是隻有一個參數成員,最好將名稱設爲」value」
  • 註解元素必須有肯定的值,能夠在註解中定義默認值,也可使用註解時指定,非基本類型的值不可爲null,常使用空字符串或0做默認值
  • 在表現一個元素存在或缺失的狀態時,定義一下特殊值來表示,如空字符串或負值

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* test註解
* @author ddk
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnnotation {
/**
* id
* @return
*/
public int id() default -1;
/**
* name
* @return
*/
public String name() default "";
}

註解處理器類庫

java.lang.reflect.AnnotatedElement

Java使用Annotation接口來表明程序元素前面的註解,該接口是全部Annotation類型的父接口。除此以外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口表明程序中能夠接受註解的程序元素,該接口主要有以下幾個實現類:

  •  Class:類定義
  •  Constructor:構造器定義
  •  Field:累的成員變量定義
  •  Method:類的方法定義
  •  Package:類的包定義

java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包全部提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義爲運行時的Annotation後,該註解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation纔會被虛擬機讀取。

AnnotatedElement 接口是全部程序元素(Class、Method和Constructor)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象以後,程序就能夠調用該對象的以下四個個方法來訪問Annotation信息:

  • 方法1: T getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的註解,若是該類型註解不存在,則返回null。
  • 方法2:Annotation[] getAnnotations():返回該程序元素上存在的全部註解。
  • 方法3:boolean is AnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false.
  • 方法4:Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的全部註釋。與此接口中的其餘方法不一樣,該方法將忽略繼承的註釋。(若是沒有註釋直接存在於此元素上,則返回長度爲零的一個數組。)該方法的調用者能夠隨意修改返回的數組;這不會對其餘調用者返回的數組產生任何影響。

註解處理器示例:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/***********註解聲明***************/

/**
* 水果名稱註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}

/**
* 水果顏色註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
* @author peida
*
*/
public enum Color{ BULE,RED,GREEN};

/**
* 顏色屬性
* @return
*/
Color fruitColor() default Color.GREEN;

}

/**
* 水果供應者註解
* @author peida
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
* @return
*/
public int id() default -1;

/**
* 供應商名稱
* @return
*/
public String name() default "";

/**
* 供應商地址
* @return
*/
public String address() default "";
}

/***********註解使用***************/

public class Apple {

@FruitName("Apple")
private String appleName;

@FruitColor(fruitColor=Color.RED)
private String appleColor;

@FruitProvider(id=1,name="陝西紅富士集團",address="陝西省西安市延安路89號紅富士大廈")
private String appleProvider;

public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}

public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}

public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}

public void displayName(){
System.out.println("水果的名字是:蘋果");
}
}

/***********註解處理器***************/
//實際上是用的反射


public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz){

String strFruitName=" 水果名稱:";
String strFruitColor=" 水果顏色:";
String strFruitProvicer="供應商信息:";

Field[] fields = clazz.getDeclaredFields();

for(Field field :fields){
if(field.isAnnotationPresent(FruitName.class)){
FruitName fruitName = (FruitName) field.getAnnotation(FruitName.class);
strFruitName=strFruitName+fruitName.value();
System.out.println(strFruitName);
}
else if(field.isAnnotationPresent(FruitColor.class)){
FruitColor fruitColor= (FruitColor) field.getAnnotation(FruitColor.class);
strFruitColor=strFruitColor+fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
}
else if(field.isAnnotationPresent(FruitProvider.class)){
FruitProvider fruitProvider= (FruitProvider) field.getAnnotation(FruitProvider.class);
strFruitProvicer=" 供應商編號:"+fruitProvider.id()+" 供應商名稱:"+fruitProvider.name()+" 供應商地址:"+fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}

/***********輸出結果***************/
public class FruitRun {

/**
* @param args
*/
public static void main(String[] args) {

FruitInfoUtil.getFruitInfo(Apple.class);

}

}

====================================
水果名稱:Apple
水果顏色:RED
供應商編號:1 供應商名稱:陝西紅富士集團 供應商地址:陝西省西安市延安路89號紅富士大廈

Java 8 中註解新特性

  • @Repeatable 元註解,表示被修飾的註解能夠用在同一個聲明式或者類型加上多個相同的註解(包含不一樣的屬性值)
  • @Native 元註解,本地方法
  • java8 中Annotation 能夠被用在任何使用 Type 的地方
1
2
3
4
5
6
7
8
9
10
11
12
 //初始化對象時
String myString = new @NotNull String();
//對象類型轉化時
myString = (@NonNull String) str;
//使用 implements 表達式時
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表達式時
public void validateValues() throws @Critical ValidationFailedException{
...
}

思惟導圖

拓展閱讀

深刻理解Java:註解 - 牛奶、不加糖 - 博客園
Java 註解基礎知識 - 簡書
【譯】從java註解分析ButterKnife工做流程 - 簡書

相關文章
相關標籤/搜索