1. 定義:
定義:註解(Annotation),也叫後設資料。一種程式碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋
2. 為什麼要使用註解
- 使用註解可以提升開發效率,因為註解中已經封裝好了我們需要的程式碼
- 當我們開發專案的時候,我們會用到很多第三方庫,裡面都用註解的形式開發的, 比如: Spring , Mybatis, butterknife , EventBus等等.我們可以去更好的閱讀他們的程式碼,瞭解底層實現原理
3. 註解的分類
3.1 按照來源分
- 標準註解(Jdk自帶的註解): 如Override, Deprecated,SuppressWarnings等
- 第三方註解:如 butterknife的@bindview,EventBus的@Subscribe(threadMode = ThreadMode.MAIN),來自第三方庫的註解
- 自定義註解: 自己寫自己用,為了裝b使用
3.2 按執行機制分 (也可以說是生命週期吧)
簡單的說下他的生命週期吧:
Java原始檔(.java檔案) ---> .class檔案 ---> 記憶體中的位元組碼
複製程式碼
- 原始碼註解:註解只在原始碼中存在,編譯成.class檔案就不存在了,也就是生命週期最短的一個
- 編譯時註解:註解在原始碼和.class檔案中都存在(如:@Override、@Deprecated、@SuppressWarings),不是通過反射實現的,不影響效能,butterknife.EventBus等都用的是編譯型註解
- 執行時註解:在執行階段還起作用,甚至會影響執行邏輯的註解,註解不僅被儲存到class檔案中,jvm載入class檔案之後,仍然存在,本質通過反射實現的,影響效能
4. 定義註解
先看一下簡單的定義一個註解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Documented
public @interface InJect {
String name();
int age() default 18;
}
複製程式碼
語法: 以 @interface 這個為代表註解
5 元註解
定義: 定義註解的註解,主要包括@Retention @Target @Document @Inherited四種,下面一一說一下這四個的意思
5.1 @Retention 定義註解的保留,也就是註解存在的生命週期.有三個
- RetentionPolicy.SOURCE :也就是上面所說的原始碼註解,只會存在原始碼內
- RetentionPolicy.CLASS :也就是存在.class檔案中,執行時不需要Vm保留
- RetentionPolicy.RUNTIME: 執行時註解將被編譯器記錄在類檔案中 在執行時可以通過反射獲取到。
5.2 @Target 定義註解作用目標 可以使用在哪裡
ElementType的列舉陣列包、類、介面、註解、列舉、欄位(類的屬性)、構造方法、普通方法、引數(方法的引數)、區域性變數. 列舉型別的常量值有
- ElementType.TYPE(類,或者介面,或者列舉上面)
- ElementType.FIELD(欄位上)
- ElementType.METHOD(方法上)
- ElementType.PARAMETER(引數)
- ElementType.CONSTRUCTOR(構造方法上)
- ElementType.LOCAL_VARIABLE(區域性變數上)
- ElementType.ANNOTATION_TYPE(註釋宣告)
- ElementType.PACKAGE(包)
- ElementType.TYPE_PARAMETER(型別引數) JDK 1.8以後才有
- ElementType.TYPE_USE(型別宣告和型別引數宣告) JDK 1.8以後才有
上面我感覺最重要的ElementType(比如GreenDao).TYPE,ElementType.FIELD(比如butterknife),ElementType.METHOD(比如EventBus),其他的可以瞭解一下
5.3 @Documented 和 @Inherited 兩個都是標示型註解
@Documented:生成API幫助文件時顯示註解
@Inherited:允許子類繼承父類的註解
6 註解的語法
6.1 以@interface為標識
6.2 變數語法
比如上面String desc() ,int age() default 18
- 成員變數必須以無參無異常的方式生明 如果有引數 直接爆紅
- 可以使用default修飾 預設值
- 成員型別可以是 基本型別。 String。 Class。 Annotation 。Enumeration(我們們主要用到基本型別,String ,class 後面兩個很少用到)
- 如果註解只有一個成員的時候,成員名必須取名為 value(), 但是如果不取名value ,不報錯,不符合規定(java界的約定成俗)
- 當有一個成員的時候 ,使用的時候,可有直接省略value 比如@SuppressWarnings("deprecation"); //不用@SuppressWarnings(value="deprecation")(java界的約定成俗)
- 註解類可以沒有成員變數,沒有成員的註解成為標識註解. 比如override 表明是繼承父類的標識
7 使用
<註解名>(<成員名1>=<值1>,<成員名2>=<值2>。。。)
public class DaHa(){
@InJect (name="哈士奇",age=1) //定義在方法上根據@Target({ElementType.FIELD,ElementType.METHOD})
public void like(){
}
}
}
複製程式碼
7.1 模仿butterknife為例簡單實現(執行時實現方式)。當然butterknife是編譯型註解,稍後再說
7.1.1 定義butterknife註解
//執行時註解
@Retention(RetentionPolicy.RUNTIME)
//宣告在變數上
@Target({ElementType.FIELD})
@Documented
public @interface BindView {
// 因為只有一個成員所以起名為value,當然了可以隨便起
int value();
}
複製程式碼
7.1.2 寫一個Demo使用這個註解
public class Demo {
@BindView(100)
View view;
@BindView(101)
View view1;
@BindView(102)
View view2;
@BindView(103)
View view3;
}
複製程式碼
7.1.3 寫一個單元測試類
@Test
public void text() throws Exception {
//1.使用類載入器載入類
Class c = null;
// c = Class.forName("nzy.cn.annotationdemo.Demo"); 或者下面的也可以得到
c = ClassLoader.getSystemClassLoader().loadClass("cn.nzy.annotationdemo.Demo");
//2.找到類中所有的成員變數
Field[] declaredFields = c.getDeclaredFields();
// 3. 迴圈所有的成員變數 檢視每個成員變數上是否存在BindView的註解 返回是boolean值
for (Field field : declaredFields) {
boolean isExist = field.isAnnotationPresent(BindView.class);
//如果存在
if (isExist) {
//4. 拿到註解的例項物件
BindView bind = (BindView) field.getAnnotation(BindView.class);
//5. 得到裡面所給你的值
int id = bind.value();
// 列印出來 變數型別 變數名字
System.out.println("field是 "+field.getType()+"----"+field.getName()+"--------" + "id是" + id);
}
}
複製程式碼
列印結果為:
field是 class android.view.View----view--------id是100
field是 class android.widget.TextView----view1--------id是101
field是 class android.widget.ImageView----view2--------id是102
field是 class android.widget.Button----view3--------id是103
複製程式碼
此時既拿到 型別, 變數名字, 跟id 然後再執行findViewById就可以得到了 以下是虛擬碼,知道大概意思就可以了
// 虛擬碼 執行不過去滴
View field.getName()= findViewById(id)
複製程式碼
7.2 簡單的介紹一下ButterKnife的原理
其實ButterKnife的大致流程跟上面所說的差不多,當然了,ButterKnife用的是編譯時期-註解處理器AbstractProcessor類, 下一篇文章再講解這個類. 其實ButterKnife最後還是執行的findViewById這個方法,他走的流程
layout -> Window -> PhoneWindow ->DecorView->在這個裡面findviewbyid
複製程式碼