【轉】JAVA反射與注解


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

前言

現在在我們構建自己或公司的項目中,或多或少都會依賴幾個流行比較屌的第三方庫,比如:Butter KnifeRetrofit 2Dagger 2GreenDao等,如果你沒用過,那你需要找時間補一下啦;有時在使用后我們會好奇他們到底是怎么做到這種簡潔、高效、松耦合等諸多優點的,當然這里我不探討它們具體怎么實現的 (可以看看我之前寫的幾篇文章) ,而關心的是它們都用到同樣的技術那就是本篇所講的反射注解,並實現的依賴注入。

閱讀本篇文章有助於你更好的理解這些大形框架的原理和復習Java的知識點。為什么要把反射放在前面講呢,實際上是因為我們學習注解的時候需要用到反射機制,所以,先學習反射有助於理解后面的知識。

JAVA反射

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

反射機制是什么

面試有可能會問到,這句話不管你能不能理解,但是你只要記住就可以了

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

用一句話總結就是反射可以實現在運行時可以知道任意一個類屬性和方法

反射機制能做什么

反射機制主要提供了以下功能:

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

Java 反射機制的應用場景

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

反射機制的優點與缺點

為什么要用反射機制?直接創建對象不就可以了嗎,這就涉及到了動態與靜態的概念

  • 靜態編譯:在編譯時確定類型,綁定對象,即通過。

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

優點

  • 可以實現動態創建對象和編譯,體現出很大的靈活性,特別是在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方法,通過一個類的全量限定名獲得

這里,c1、c2、c3都是Class的對象,他們是完全一樣的,而且有個學名,叫做Code的類類型(class type)。
這里就讓人奇怪了,前面不是說Code是Class的對象嗎,而c1、c2、c3也是Class的對象,那么Code和c1、c2、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工作流程 - 簡書

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM