由于年前各種原因,博客一直沒(méi)法更新。在這里我表示十分的歉意,希望各位能夠繼續(xù)關(guān)注我的博客。我也將跟大家一起加油,努力!
我相信 Java 注解大家都不會(huì)陌生,許多開(kāi)源的第三方框架中都有它的身影如:butterknife,eventbus,retrofit2, dagger2 等等...
有這樣一個(gè)細(xì)節(jié)你是否注意到了呢?
在 Activity 中重寫(xiě) onCreate 方法:
@Override //標(biāo)記注解
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Override 這里就用到了非常常見(jiàn)的 標(biāo)記注解,我們繼續(xù)看看 Override 注解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
@Retention ,@Target 又分別有什么含義呢?請(qǐng)繼續(xù)往后面看。
什么是注解
注解(Annotation),也叫(metadata)元數(shù)據(jù)。一種代碼級(jí)別的說(shuō)明。它是JDK1.5及以后版本引入的一個(gè)特性,與類(lèi)、接口、枚舉是在同一個(gè)層次。它可以聲明在包、類(lèi)、字段、方法、局部變量、方法參數(shù)等的前面,用來(lái)對(duì)這些元素進(jìn)行說(shuō)明,注釋。這些信息被存儲(chǔ)在Annotation的“name=value”結(jié)構(gòu)對(duì)中。
那么你可能又有疑問(wèn)什么是(metadata)元數(shù)據(jù)呢?
這個(gè)解釋起來(lái)比較抽象,我的理解是:“數(shù)據(jù)的數(shù)據(jù)”。
那它有什么作用呢,如果要對(duì)于元數(shù)據(jù)的作用進(jìn)行分類(lèi),還沒(méi)有明確的定義,不過(guò)我們可以根據(jù)它所起的作用,大致可分為三類(lèi):
編寫(xiě)文檔:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)生成文檔。
代碼分析:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)對(duì)代碼進(jìn)行分析。
編譯檢查:通過(guò)代碼里標(biāo)識(shí)的元數(shù)據(jù)讓編譯器能實(shí)現(xiàn)基本的編譯檢查
注解的分類(lèi)
根據(jù)注解的參數(shù)個(gè)數(shù)分類(lèi):
標(biāo)記注解。一個(gè)沒(méi)有成員的 Annotation,這種類(lèi)型僅僅使用自身的存在與否來(lái)為我們提供信息。標(biāo)記注解非常常見(jiàn),比如上面所說(shuō)的 @Override
單值注解。成員的參數(shù)為單個(gè)參數(shù)
完整注解。成員的參數(shù)為多個(gè)參數(shù)
根據(jù)注解使用的方法和用途分類(lèi):
JDK內(nèi)置系統(tǒng)注解
元注解
自定義注解
它們都不會(huì)直接影響到程序的語(yǔ)義,只是作為注解(標(biāo)識(shí))存在,我們可以通過(guò)反射機(jī)制編程實(shí)現(xiàn)對(duì)這些元數(shù)據(jù)(用來(lái)描述數(shù)據(jù)的數(shù)據(jù))的訪問(wèn)。另外,你可以在編譯時(shí)選擇代碼里的注解是否只存在于源代碼級(jí),或者它也能在class文件、或者運(yùn)行時(shí)中出現(xiàn)(SOURCE/CLASS/RUNTIME)。下面我具體根據(jù)注解使用的方法和用途分類(lèi)來(lái)講解。
內(nèi)置注解
內(nèi)置注解分為三類(lèi):
@Override
@Deprecated
@SuppressWarnings
1、@Override
它的作用是對(duì)覆蓋超類(lèi)中方法的方法進(jìn)行標(biāo)記,如果被標(biāo)記的方法并沒(méi)有實(shí)際覆蓋超類(lèi)中的方法,則編譯器會(huì)發(fā)出錯(cuò)誤警告。換句話(huà)理解就是重寫(xiě)父類(lèi)方法,方法前的標(biāo)記。
@Override
protected void onStart() {
super.onStart();
}
2、@Deprecated
它的作用是對(duì)不應(yīng)該再使用的方法添加注解,當(dāng)編程人員使用這些方法時(shí),將會(huì)在編譯時(shí)顯示提示信息,它與javadoc里的@deprecated標(biāo)記有相同的功能。
public class User {
@Deprecated
public static String getName(){
return "user";
}
}
調(diào)用 getName 方法:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//@Deprecated 方法已經(jīng)過(guò)時(shí) 出現(xiàn)中劃線
User.getName();
}
}
3、@SuppressWarnings
其參數(shù)有:
- deprecation 使用了過(guò)時(shí)的類(lèi)或方法時(shí)的警告
- unchecked 執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告
- fallthrough 當(dāng) switch 程序塊直接通往下一種情況而沒(méi)有 break 時(shí)的警告
- path 在類(lèi)路徑、源文件路徑等中有不存在的路徑時(shí)的警告
- serial 當(dāng)在可序列化的類(lèi)上缺少serialVersionUID 定義時(shí)的警告
- finally 任何 finally 子句不能正常完成時(shí)的警告
- all 關(guān)于以上所有情況的警告
public class User {
@SuppressWarnings("deprecation")
public static int getAge() {
return 18;
}
}
元注解
元注解就是定義注解的注解,由 java API 提供,分別有四個(gè)元注解:
@Target
@Retention
@Documented
@Inherited
1、@Target
用于描述注解的使用范圍。修飾的對(duì)象范圍:packages、types(類(lèi)、接口、枚舉、Annotation類(lèi)型)、類(lèi)型成員(方法、構(gòu)造方法、成員變量、枚舉值)、方法參數(shù)和本地變量(如 catch 參數(shù))。
它的值在枚舉類(lèi) ElemenetType 中:
- CONSTRUCTOR: 用于描述構(gòu)造器
- FIELD : 用于描述域
- LOCAL_VARIABLE: 用于描述局部變量
- METHOD : 用于描述方法
- PACKAGE : 用于描述包
- PARAMETER : 用于描述參數(shù)
- TYPE : 用于描述類(lèi)、接口(包括注解類(lèi)型) 或enum聲明
Mode類(lèi)可以注解類(lèi)的成員變量:
@Target(ElementType.FIELD)
@Documented
public @interface Mode {
public int value() default 0;
}
Person可以注解類(lèi)、接口(包括注解類(lèi)型)、或者enum聲明:
@Target(ElementType.TYPE)
public @interface People {
public String value() default "";
}
2、@Retention
表示需要在什么級(jí)別保存該注釋信息,用于描述注解的生命周期(即:被描述的注解在什么范圍內(nèi)有效),定義了該Annotation被保留的時(shí)間長(zhǎng)短:某些Annotation僅出現(xiàn)在源代碼中,而被編譯器丟棄;而另一些卻被編譯在class文件中;編譯在class文件中的Annotation可能會(huì)被虛擬機(jī)忽略,而另一些在class被裝載時(shí)將被讀取。
RetentionPoicy取值:
- SOURCE : 在源文件中有效(即源文件保留)
- CLASS : 在class文件中有效(即class保留)
- RUNTIME : 在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
Page 注解的RetentionPolicy 的值為 RUNTIME,這樣注解處理器可以通過(guò)反射,獲取到該注解的屬性,從而做一些運(yùn)行時(shí)的邏輯處理。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Page {
public int value() default 1;
}
3、@Documented
用于描述其它類(lèi)型的annotation應(yīng)該被作為被標(biāo)注的程序成員的公共API,Documented 是一個(gè)標(biāo)記注解,沒(méi)有成員。將此注解包含在 javadoc 中 ,它代表著此注解會(huì)被javadoc工具提取成文檔。在doc文檔中的內(nèi)容會(huì)因?yàn)榇俗⒔獾男畔?nèi)容不同而不同。
@Documented
public @interface Page {
public int value() default 0;
}
4、@Inherited
允許子類(lèi)繼承父類(lèi)中的注解,是一個(gè)標(biāo)記注解,@Inherited闡述了某個(gè)被標(biāo)注的類(lèi)型是被繼承的。如果一個(gè)使用了@Inherited修飾的annotation類(lèi)型被用于一個(gè)class,則這個(gè)annotation將被用于該class的子類(lèi)。
@Inherited annotation類(lèi)型是被標(biāo)注過(guò)的class的子類(lèi)所繼承。類(lèi)并不從它所實(shí)現(xiàn)的接口繼承annotation,方法并不從它所重載的方法繼承annotation
當(dāng)@Inherited annotation類(lèi)型標(biāo)注的annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強(qiáng)了這種繼承性。如果我們使用java.lang.reflect去查詢(xún)一個(gè)@Inherited annotation類(lèi)型的annotation時(shí),反射代碼檢查將展開(kāi)工作:檢查class和其父類(lèi),直到發(fā)現(xiàn)指定的annotation類(lèi)型被發(fā)現(xiàn),或者到達(dá)類(lèi)繼承結(jié)構(gòu)的頂層。
自定義注解
使用@interface自定義注解時(shí),自動(dòng)繼承了java.lang.annotation.Annotation接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱(chēng)就是參數(shù)的名稱(chēng),返回值類(lèi)型就是參數(shù)的類(lèi)型(返回值類(lèi)型只能是基本類(lèi)型、Class、String、enum)??梢酝ㄟ^(guò)default來(lái)聲明參數(shù)的默認(rèn)值。以上所有例子都屬于自定義注解。自定義注解具有以下固定格式:
public @interface 注解名{注解體}
- 所有基本數(shù)據(jù)類(lèi)型(int,float,boolean,byte,double,char,long,short)
- String類(lèi)型
- Class類(lèi)型
- enum類(lèi)型
- Annotation類(lèi)型
- 以上所有類(lèi)型的數(shù)組
注意:只能有public或默認(rèn)(default)這兩個(gè)訪問(wèn)權(quán)修飾,參數(shù)成員只能用以上6種類(lèi)型,如果只有一個(gè)參數(shù)成員,最好把參數(shù)名稱(chēng)設(shè)為"value"。
Shade 形狀注解:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Shade {
public enum ShadeType {
Triangle("三角"),
Four("四邊"),
Five("五角");
private String type;
ShadeType(String type) {
this.type = type;
}
@Override
public String toString() {
return type;
}
}
public ShadeType shader() default ShadeType.Triangle;
}
讀取注解
這里我們將使用反射去讀取注解。Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素。該接口主要有如下幾個(gè)方法:
default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
判斷該程序元素上是否包含指定類(lèi)型的注解,存在則返回true,否則返回false。
<T extends Annotation> T getAnnotation(Class<T> var1);
返回該程序元素上存在的、指定類(lèi)型的注解,如果該類(lèi)型注解不存在,則返回null。
Annotation[] getAnnotations();
返回該程序元素上存在的所有注解。
Annotation[] getDeclaredAnnotations();
返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒(méi)有注釋直接存在于此元素上,則返回長(zhǎng)度為零的一個(gè)數(shù)組。)該方法的調(diào)用者可以隨意修改返回的數(shù)組;這不會(huì)對(duì)其他調(diào)用者返回的數(shù)組產(chǎn)生任何影響。
public static void getInfo(Class<?> clazz) {
// 獲取該類(lèi)所有聲明的方法
Field[] fields = clazz.getDeclaredFields();
if (fields == null) return;
for (Field field : fields) {
if (field.isAnnotationPresent(Mode.class)) {
Mode m0 = field.getAnnotation(Mode.class);
System.out.println("****name=" + m0.value());
}
if (field.isAnnotationPresent(Shade.class)) {
Shade s0 = field.getAnnotation(Shade.class);
System.out.println("****shade=" + s0.shader().toString());
}
if (field.isAnnotationPresent(People.class)) {
People p0 = field.getAnnotation(People.class);
System.out.println("****name=" + p0.Name() + "**age=" + p0.Age() + "**price=" + p0.Price());
}
}
}
使用自定義注解:
public class User {
@Mode(value = "小石頭")
public String name;
@Shade(shader = Shade.ShadeType.Five)
public String shape;
@People(Age = 18, Price = 100f, Name = "小寶")
public int profile;
}
運(yùn)行:
getInfo(User.class);
打印:
****name=小石頭
****shade=五角
****name=小寶**age=18**price=100.0
注意:使用反射去讀取注解,必須將Retention的值選為Runtime。