註解的詳細介紹

android大哥發表於2018-04-28

1. 定義:

定義:註解(Annotation),也叫後設資料。一種程式碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、介面、列舉是在同一個層次。它可以宣告在包、類、欄位、方法、區域性變數、方法引數等的前面,用來對這些元素進行說明,註釋

2. 為什麼要使用註解

  1. 使用註解可以提升開發效率,因為註解中已經封裝好了我們需要的程式碼
  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
複製程式碼

相關文章