1. 程式人生 > >Java和Android中的註解

Java和Android中的註解

1.引言

從JDK1.5開始,引入了註解類Annotation,Annotation其實是一種介面,可以作用於類、方法、屬性等等 ,它可以通過反射機制來訪問annotation資訊,獲取所加上註解資訊,做相應的操作。相當於給相關的作用物件打上“tag”,使用方便,作用廣泛。

2.java.lang中的註解

     在java.lang中,用到三種註解類,即常用到的Deprecated,Override和SuppressWarnings.

     2.1 @Deprecated 過時

     在使用Eclipse或AS編寫程式過程中,有一些方法編寫出來之後被畫上了中劃線,表示該方法已過時,建議使用新的一些方法代替。
     如:viewPager.setOnPageChangeListener()

方法在安卓API23中已過時,在原始碼中的該方法上赫然多了一個註解@Deprecated,可使用addOnPageChangeListener()方法代替。
     如果使用javac命令編譯,會彈出:”注意:使用或覆蓋了已過時的API“

     2.2 @Override 複寫

     這是開發過程中最常遇到的註解了,表示複寫父類中的方法。如果方法上有這條註解但沒有重寫父類方法,則會生成一條錯誤訊息。這個註解有效地保證了方法一定會被複寫。比如開發過程中,手動在子類中複寫父類方法,只正確寫出了方法名稱,未寫正確方法的引數,則相當於方法的過載,並不是複寫。這種問題如果在功能上有錯誤產生,在檢查過程中是很難找到的。使用@Override註解,有效地表明,此方法是複寫父類的方法,不會產生手動的錯誤問題。

     2.3 @SuppressWarnings 阻止警告

     阻止了彈出的警告,比如遮蔽對上述過時的提示,可在呼叫setOnPageChangeListener()方法之上加上@SuppressWarnings("deprecation"),這樣雖然在IDE中仍以中劃線的形式表示,但如果使用javac命令就不再提示警告。

3.元註解

     那麼以上的註解類的原始碼又是什麼樣的呢,開啟Override發現其程式碼如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}


這裡可以發現註解類的定義方法以及註解類上所加的註解——元註解。元註解有四個,位於java.lang.annotation包中:Documented,Inherited,Retention,Target

     [email protected]

     表示註解作用的目標。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

註解可以作用的目標由列舉類ElementType決定,可作用於類、方法、屬性等,陣列表示註解可以有多個作用域。

public enum ElementType {
    TYPE,// 類、介面、註解型別或列舉
     FIELD, //屬性
     METHOD, //方法
     PARAMETER,// 用於描述引數
     CONSTRUCTOR,//構造方法
      LOCAL_VARIABLE,//區域性變數
     ANNOTATION_TYPE,//註解類
     PACKAGE //包
}

註解作用於類時,定義@Target(ElementType.TYPE),ElementType .TYPE表示型別,Class,Interface等都實現了java中的Type介面,因此ElementType.TYPE 表示註解作用於這些"類"(並不是單純的Class類)

     [email protected]

     表示註解的作用時段

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

由此可見,Retention定義的是RetentionPolicy型別的資料,RetentionPolicy是一個列舉型別,包括SOURCE,CLASS,RUNTIME三個型別。表示註解保留的階段。
SOURCE:表示註解只在原始檔中保留
CLASS:表示註解保留到.class檔案中
RUNTIME:表示註解一直保留到記憶體中,類載入器把.class檔案載入到記憶體中產生的位元組碼中要保留註解資訊。
可想而知,@Override僅在寫程式碼時用到,其Retention中的值為RetentionPolicy.SOURCE,@SuppressWarnings也只是編譯器使用的,保留到SOURCE階段。而@Deprecated在記憶體中也需要保留,編譯器需要得知方法是否過時,從載入到記憶體中的二進位制檔案中獲取,因而要保留到RUNTIME階段

     [email protected] 

     註解只是一個標記的話,那麼使用javadoc生成文件時,這些註解都不會存在於文件中,要使註解在文件中也存在,就可以在使用了註解的作用物件上加上@Documented這個註解。javadoc所生成的文件就會帶上註解資訊。

     [email protected]

     該註解表示子類可以整合載入父類上的註解。但要注意:      1.註解定義在類上面,子類是可以繼承該註解      2.註解定義在方法上面,子類也可以繼承該註解,但是如果子類複寫了父類中定義了註解的方法,那麼子類將無法繼承該方法的註解,也就是說,子類在複寫父類中被@Inherited標註的方法時,會將該方法上面的註解覆蓋掉      3.Interface的實現類(implements實現)無法繼承介面中所定義的被@Inherited標註的註解

4.註解的引數

在註解後為註解增加引數,如上述的@Retention、@Target後的括號中就是註解的引數。 引數的型別,支援基本資料型別、String型別、Class型別、enum型別、Annotation型別以及上述所有型別的陣列。 當註解只有一個引數時,可以用value作為引數名稱,在使用引數時,只用在括號中寫引數的值而不用寫引數名稱。若存在多個引數時,需寫上對應的引數名,並賦值,如@Subscribe(tag="xx"),若引數為陣列型別,則需賦值形如:@Target({ElementType.TYPE,ElementType.METHOD})

5.自定義註解

     從上面的註解中,也大致看到了註解的定義方式。      1.使用@interface定義一個註解類,其內部自行繼承了Annotation類。      2.在該類中定義註解的引數,定義方式很像方法。      3.註解元素必須有確定的值,要麼在定義註解的預設值中指定,要麼在使用註解時指定,非基本型別的註解元素的值不可為null。因此, 使用空字串或0作為預設值是一種常用的做法。定義一些特殊的值,例如空字串或者負數,表示某個元素不存在引數設定預設值時,後面跟上default即可設定預設值。      4.當註解的引數只有一個時,建議用value作為引數名,這樣在使用註解時,可以直接寫引數的值      接下來自定義一個註解類:
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Car{
     String name() default "";
     int number() default -1;
}

再定義一個類去使用這個註解:
@Car(name="好車",number=666)
public class CarBMW(){
     
}

那麼如何獲取這些註解值呢?有如下的方法:
if(CarBMW.class.isAnnotationPresent(Car.class)){//判斷CarBMW類是否有註解類Car
     Car carAnnotation=(Car)CarBMW.class.getAnnotation(Car.class);//獲取註解例項物件
     Log.v("Shawn",carAnnotation.name());//獲取name引數的值
     Log.v("Shawn",carAnnotation.number());//獲取number引數的值
}

5.1 JAVA8中註解的補充

在JAVA8中,增加了TypeAnnotation,在 Java 8 之前的版本中,只能允許在宣告式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化物件時 (new),物件型別轉化時。
//初始化物件時
String myString = new @NotNull String();
//物件型別轉化時
myString = (@NonNull String) str;

這點上有點類似Android中的註解使用。
而ElementType中增加了倆個值ElementType.TYPE_PARAMETER和ElementType.TYPE_USE 正是為此功能實現而產生的。
ElementType.TYPE_PARAMETER 表示這個 Annotation 可以用在 Type 的宣告式前,而 ElementType.TYPE_USE 表示這個 Annotation 可以用在所有使用 Type 的地方(如:泛型,型別轉換等)。
Type Annotation 也可以通過設定 Retention 在編譯後保留在 class 檔案中(RetentionPolicy.CLASS)或者執行時可訪問(RetentionPolicy.RUNTIME)。但是與之前不同的是,Type Annotation 有兩個新的特性:在本地變數上的 Annotation 可以保留在 class 檔案中,以及泛型型別可以被保留甚至在執行時被訪問。
此外,JAVA8中還增加了一個特性RepeatingAnnotation,允許為同一個宣告式或者型別加上相同的 Annotation。
例如:一個類可以被兩個人使用:
@Worker(role="XiaoMing")
@Worker(role="XiaoHong")
public class Person {
     ...
}

需要需要重複標註特性的 Annotation 前加上 @Repeatable 標籤:
@Repeatable(Workers.class)
public @interface Worker{
    String role();
}

@Repeatable 標籤後括號中的值即為指定的 Container Annotation 的型別。在這個例子中,Container Annotation 的型別是 Workers,Java 編譯器會把重複的 Worker 物件儲存在 Workers中。
Workers中必須定義返回陣列型別的value()方法。陣列中元素的型別必須為對應的 Repeating Annotation 型別。具體示例如下:
public @interface Workers{
    Worker[] value();
}

獲取註解的方法有兩種:

一種方式是用過 上述的的 getAnnotations(Class<T>) 方法一次性返回 Repeating Annotation。

另一種方式是通過 AnnotatedElement 介面的 getAnnotationByType(Class<T>) 首先獲得 Container Annotation,然後再通過 Container Annotation 的 value 方法獲得 Repeating Annotation。

Workers workers= Person.class.getAnnotation(Workers.class);
Worker[] workers2= Person.class.getAnnotationsByType(Worker.class);
以上關於JAVA8中的註解參考http://www.ibm.com/developerworks/cn/java/j-lo-java8annotation/index.html

6.Android中的註解

6.1兩個常見的註解

Android從API16引入了annotation包,包括兩個註解@TargetApi和@SuppressLint @TargetApi (Build.VERSION_CODES.XX)用於遮蔽某一新api中才能使用的方法報的lint檢查出現的錯誤。 @SuppressLint("NewApi") 遮蔽一切新api中才能使用的方法報的android lint錯誤 當然,這兩個註解的作用僅僅是遮蔽lint錯誤,在方法中還要判斷版本做不同的操作,比如:
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.XX) {
     //高於XX版本進行的操作
 } else {
     //低於XX版本進行的操作
 }

6.2Android中用到的註解

API19引入了很多註解,起初是以jar包形式引入的,放在SDK目錄下sdk\extras\android\m2repository\com\android\support\support-annotations\ 在API21之後,直接放入了annotation包中 Android中的註解分為八類,在API23中有42個註解類

6.2.1 @CallSuper

     要求方法必須呼叫父類方法,可想而知,Activity的onCreate()方法有此註解標識。

6.2.2 @NonNull和@Nullable     

     定義一個變數或物件可以為空或不可為空,用在方法上表示方法可否返回空。

6.2.3 資源類註解

     @AnimatorRes,@AnimRes,@AnyRes,@ArrayRes,@AttrRes,@ BoolRes,@ ColorRes,@ DimensRes,@ DrawableRes,@ FractionRes,@ IdRes,@ IntegerRes,@ InterpolatorRes,@ LayoutRes,@ MenuRes,@ PluralsRes,@ RawRes,@ StringRes,@ StyleableRes,@StyleRes,@ TransitionRes,@ XmlRes 這部分註解是非常有實用性的,有22個,用法如:
getDrawable(@DrawableRes int id)  //getDrawable方法限定了傳入的引數必須是Drawable資原始檔
另外還有個@ColorInt,限制傳入Hex顏色值。 在開發過程中需要傳入某種資原始檔時,用此類註解,是很有限制作用的做法。

[email protected]和@StringDef

提到最常用的Toast,在API21之前,一般可以這麼寫 Toast.makeText(context, "msg", 0).show(); 但是21之後,0突然被劃紅線了,檢視原始碼發現,之前是這麼寫的:
public static final int LENGTH_SHORT = 0;
public static final int LENGTH_LONG = 1;

makeText方法中單純地寫著int duration引數,而21之後,成了這樣:
@IntDef({LENGTH_SHORT, LENGTH_LONG})
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}

在makeText的duration引數之前加上了註解@Duration,這就要求傳入Duration中的值才不會報警告。 可以想象,@IntDef和@String Def中放入了一個value陣列。 在開發中,這兩個註解就可以用來限制列舉值了。

6.2.5 範圍約束@FloatRange、@IntRange和@Size

前兩個限制了數字的範圍,比如可以用來限制傳入引數值的範圍, @Size可以用來限定集合的大小或者限定字串的長度,還可以做其他限制: 1.限制最小最大數量:@Size(min=1,max=10 ) 2.數量必須是2的倍數(偶數): @Size(multiple=2)

6.2.6 程序類註解

@UiThread,@BinderThread,@MainThread,@WorkerThread 用來限定方法或類在指定的執行緒型別中被呼叫,比如 AsyncTask中的方法就有如下的限定:
@WorkerThread
protected abstract Result doInBackground(Params... params){
     //doInBackground
}
@MainThread
protected void onProgressUpdate(Progress... values) {
     //onProgressUpdate
}

[email protected]

該註解意味著需要對方法的返回值進行處理,例如:有個方法為
@CheckResult
public boolean checkValid(String value){
     return TextUtils.isEmpty(value);
}

當呼叫者直接呼叫checkValid("abc");時會報錯,這意味著,需要對方法的返回值進行處理,無論返回true或false,需要進行後續操作,不能單單呼叫一句checkValid("abc");

6.2.8 許可權註解@RequiresPermission

如果方法的呼叫需要許可權,可以加這個註解,當需要幾個許可權中的一個時,使用anyOf,如 @RequiresPermission(anyOf = { Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}) 若需要多個許可權,使用allOf,如 @RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE})
若需要單獨的標註讀和寫的許可權訪問,所以可以用@Read或者@Write標註每一個許可權需求

6.2.9 @SdkConstant

表示一個常量欄位應該被輸出在SDK工具中使用,例如: 新增一個自定義的Action:
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 
public static final String ACTION_MY_TEST = "android.intent.action.MY_TEST"; 

則可以這樣使用:
Intent myTest = new Intent(Intent.ACTION_MY_TEST); 
mContext.sendBroadcast(myTest);

[email protected]

用於類的註解,表示該類是自定義的Widget類 此外還有兩個註解即上述的@TargetApi和@SuppressLint不再贅述。

7.總結

綜上,註解部分就介紹完了。註解在開發過程中有很強的實用性,使用註解可以明確開發過程中的一些資料型別,還可以對一些資料進行限制,從而提高開發效率。