前言
??平時在開發(fā)中接觸過許多的注解,如@Override,@Nullable等,但自己代碼中還沒怎么用過。所以,就想著學習學習,然后用一下。
什么是注解?
??注解是一種將元數(shù)據(jù)與程序元素關聯(lián)在一起的安全的類似注釋的機制。那什么是元數(shù)據(jù)?元數(shù)據(jù)就是描述數(shù)據(jù)的數(shù)據(jù),比如下面的代碼,toString方法是數(shù)據(jù),@Override就是描述toString方法的元數(shù)據(jù)。
@Override
public String toString() {
return super.toString();
}
??那程序元素都是指哪些呢?除了上面示例中說的方法,還可以是指類、變量、方法參數(shù)、構造方法以及包聲明(用于對包聲明進行說明的注解,我見的不多)。
注解的作用
??上面說了,注解是可以與程序元素進行關聯(lián),也可以對程序元素進行描述(注意,這里說的描述可不僅僅是指對程序元素進行說明,也會對程序元素有約束作用)。那有這些能力具體有什么用呢?首先,它可以用來生成文檔,一般我們在寫方法注釋時,可能會在其中引入一些其它的類、方法、成員變量等進行說明,這時候會用到@link,也可能會對方法的參數(shù)、返回值類型進行說明,這就用到了@param和@return。比如handler的某個obtainMessage方法,其注釋時如下這樣的:
/**
*
* Same as {@link #obtainMessage()}, except that it also sets the what and obj members
* of the returned Message.
*
* @param what Value to assign to the returned Message.what field.
* @param obj Value to assign to the returned Message.obj field.
* @return A Message from the global message pool.
*/
public final Message obtainMessage(int what, Object obj){
...
}
??這個注釋就包含了對注解如@link,@param,@return的使用。注解的另一個作用是,“跟蹤代碼的依賴性,實現(xiàn)替代配置文件的功能”。比如說某個字段使用了注解,就可將其實例化的對象賦值給它,或者將android布局文件中的view對象賦值給它。還有一個作用就比較簡單了,就是配合編譯器在編譯階段對代碼的檢查,比如@Override,父類及其祖類如果沒有此方法而子類的方法使用了該注解,編譯器就會報錯了。
??其實,用于描述數(shù)據(jù)的元數(shù)據(jù)可以使用注解的形式,也可以使用xml的形式。xml可以做到配置分離,即代碼和配置文件分開,比較適合給應用設置很多的常量或參數(shù);而注解是緊耦合的,當需要元數(shù)據(jù)與代碼緊密耦合在一起時使用注解就比較好。java中的一個示例,把某個方法聲明為服務,使用注解就比較好。另外,元數(shù)據(jù)使用xml形式有可能由于其配置分離的不耦合或松耦合導致xml文件過多難于維護,使用注解就可以有效避免這個問題。
什么是元注解?
??上面說的是注解及其作用,而注解是怎么定的呢?這就要先了解一下元注解。所謂“元注解”,其實就是用于定義注解的注解。java總共給出了我們4種元注解:
- @Documented,注解是否將包含在javaDoc中
- @Retention,什么時候使用該注解
- @Target,注解用于什么地方
- @Inherited,是否允許子類繼承該注解
??@Documented,是否將注解信息添加到java文檔中;
??@Retention,定義注解的生命周期的。有三個取值:
- RetentionPolicy.SOURCE,這個值表示的是此注解在源碼階段使用,在代碼編譯階段就被丟棄了,不會被寫到字節(jié)碼中。示例就是@Override,@SuppressWarnings。
- RetentionPolicy.CLASS,這種注解會被寫到字節(jié)碼中,在字節(jié)碼階段使用,在類加載器時丟棄,也就是說字節(jié)碼文件被加載進內存之后的Class對象通過反射是不能獲取到此注解的。比較重要的一點,如果定義注解時沒有使用@Retention,那默認就是這種CLASS類型的。
- RetentionPolicy.RUNTIME,在程序運行階段使用,即便是字節(jié)碼加載進內存之后的Class對象也會有該注解,通過反射也會獲取到此注解,這種注解就是一直存在著的。需要注意的一點是,一般我們自己寫的自定義注解需要設置成這個值。
??@Target,表示注解作用于程序的哪個元素(類,成員變量,構造方法,方法,方法參數(shù),局部變量,包聲明)。一般的取值有下面這么多,定義注解但不使用@Target,那就表明這個注解可以作用在任何程序元素上。(后面會發(fā)現(xiàn),還有注解類型等)
- ElementType.TYPE,作用目標為類、接口、enum聲明;
- ElementType.FIELD,作用于成員變量(包括enum實例);
- ElementType.CONSTRUCTOR,作用于構造方法;
- ElementType.METHOD,作用于方法;
- ElementType.PARAMETER,作用于方法參數(shù);
- ElementType.LOCAL_VARIABLE,作用于局部變量;
- ElementType.PACKAGE,作用于包聲明;
??@Inherited,使用了@Inherited的注解不僅作用于當前該程序元素,還會作用于繼承或重寫該程序元素的子程序元素。比如說,使用了@Inherited的某注解是作用于類的,那么此注解也會約束到該類的子類。
注解是怎么定義呢?
??上面主要說的是,定義注解的注解-元注解。那一般一個注解是怎么定義的呢?以@Override為例:
package java.lang;
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
??@Target的取值是ElementType.METHOD,說明此注解是作用于類中方法上的;@Retention的取值是RetentionPolicy.SOURCE,說明此注解在源碼階段使用,在編譯階段就會被丟棄。使用@interface定義了一個注解,但注解的內容卻啥都沒有。那它是怎么去校驗當前類的父類及祖類有沒有聲明此方法的?這個校驗的具體實現(xiàn)在哪里呢?這里要說的是,注解元數(shù)據(jù)與業(yè)務邏輯是無關的,它本身在聲明時定義屬性信息(類、方法、包、域),在被使用時給這些屬性信息賦值,而拿到這個注解及其屬性信息并進行一些邏輯操作的是另一些代碼。這樣說,有些抽象。拿一個別人的例子來講吧:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface Todo {
//這兩個枚舉是提供常量值的,下面那三個是注解的方法,自帶默認值
public enum Priority {LOW, MEDIUM, HIGH}
public enum Status {STARTED, NOT_STARTED}
String author() default "Yash";
Priority priority() default Priority.LOW;
Status status() default Status.NOT_STARTED;
}
??上面是一個自定義注解的聲明,作用于方法,運行期間也一直都會存在,然后其中定義了一些屬性:author、priority、status(3個帶默認值的方法)。下面這段代碼是對上述自定義注解的使用,也就是給屬性賦值的階段:
//以此給注解的各屬性賦值
@Todo(priority = Todo.Priority.MEDIUM, author = "Yashwant", status = Todo.Status.STARTED)
public void incompleteMethod1() {
//Some business logic is written
//But it’s not complete yet
}
??我們可以通過反射的方式對這些注解屬性值進行使用
//這個類是使用了@Todo注解的方法所在的類
Class businessLogicClass = BusinessLogic.class;
//獲取此類中的可訪問到的方法并進行遍歷
for(Method method : businessLogicClass.getMethods()) {
//當前遍歷到的此方法是否有Todo這個注解,有此注解就輸出一些信息
Todo todoAnnotation = (Todo)method.getAnnotation(Todo.class);
if(todoAnnotation != null) {
System.out.println(" Method Name : " + method.getName());
//調用了注解的各個方法
System.out.println(" Author : " + todoAnnotation.author());
System.out.println(" Priority : " + todoAnnotation.priority());
System.out.println(" Status : " + todoAnnotation.status());
}
}
??說明幾點:在反射獲取此注解時,注解的表現(xiàn)就像一個正常的類,有Todo.class,也有Todo類型;獲取方法的注解使用的是Method的getAnnotation方法;原來注解中聲明的方法,在獲取了注解對象后直接注解對象.聲明的方法就可以調用得到。
??繼續(xù)回到上述問題:@Override校驗當前類的父類及祖類有沒有聲明此方法的具體代碼在哪里?上面的代碼,Todo注解是我們自定義的,被使用的具體實現(xiàn)也是在我們自己寫的代碼,而@Override這樣的注解,是java本來就有的,在編譯階段丟棄,在編譯(javac)階段之前使用,那么基本可以猜測,其具體實現(xiàn)是由jdk或jre的某一部分來實現(xiàn)的(這是廢話,只是目前我也不能確定具體是哪部分)。另外,注解一般是跟編譯器配合做出警告提示的,這里面可能就涉及到編譯器和jdk或jre的交互了(又是猜的)。
自定義注解在使用時還需注意的點
??怎么定義一個注解?
public @interface 注解名 {定義體}
??使用@interface聲明注解時,就自動繼承了java.lang.annotation.Annotation接口,其它的細節(jié)會由編譯器去完成。
??注解屬性都支持哪些類型呢?
- 所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short)
- String
- Class(獲取Class對象)
- enum
- Annotation(注解類型)
- 以上所有類型的數(shù)組類型
??另外,注解屬性只能使用public和default兩個修飾符,另外當某注解只定義了一個屬性時,最好使用下面的參數(shù)聲明和使用:
//定義只有一個屬性的注解
@interface Author{
String value();
}
//使用此注解
@Author("Yashwant")
public void someMethod() {
}
android中的注解庫是什么?常見的注解都有哪些?
??android的注解庫就是support annotations,是從support Library 19.1版本開始引入的。其中的注解大致可分為下面幾類:
- Nullness注解
- 資源類型注解
- Typedef注解
- Value Constraints注解
- 線程相關注解
- 顏色注解
- 權限注解
- 覆寫方法注解
- 返回值注解
- 其它一些注解
??下面逐一對上述分類進行說明:
??Nullness注解
??這個類型的注解有兩個:@Nullable和@NonNull。前者表示目標程序元素可為空,后者表示目標程序元素不能為空。它們的作用范圍是一樣的,都可作用于方法、方法參數(shù)、成員變量、局部變量、注解、包聲明。如果傳入?yún)?shù)不合法,就會報出警告提示。
??這類注解很好理解,就是用來限制目標程序元素是否可傳空的。
??資源類型注解
??這里說的資源是app中諸如字符串、style、layout、drawable等資源,它們向程序代碼引用它們的地方提供的是整數(shù),R.string.xxx的值為整數(shù),R.style.yyy的值也為整數(shù),這樣就可能向需要R.string.xxx的地方提供了R.style.yyy造成錯誤。為了避免這種錯誤,我們可以對引用資源處的參數(shù)添加資源類型注解,這樣該傳遞style整數(shù)的只能穿style整數(shù),否則編譯器會報錯。因為資源類型有很多種,所以資源類型的注解也有很多種,共22種。
- AnimatorRes:animator資源類型
- AnimRes:ani資源類型
- AnyRes:任意資源類型
- ArrayRes:array資源類型
- AttrRes:attr資源類型
- BoolRes:boolean資源類型
- ColorRes:color資源類型
- DimenRes:dimen資源類型
- DrawableRes:drawable資源類型
- FractionRes:fraction資源類型
- IdRes:id資源類型
- IntegerRes:integer資源類型
- InterpolatorRes:interpolator資源類型
- LayoutRes:layout資源類型
- MenuRes:menu資源類型
- PluralsRes:plural資源類型
- RawRes:raw資源類型
- StringRes:string資源類型
- StyleableRes:styleable資源類型
- StyleRes:style資源類型
- TransitionRes:transition資源類型
- XmlRes:xml資源類型
??各種各樣的注解,用來標注各種各樣的資源類型。注解標注了某參數(shù),那么該參數(shù)就只能接受該資源類型的參數(shù),否則編譯器就會報錯。另外,需要說的一點是,資源類型的目標程序元素一般是方法、方法參數(shù)、成員變量、局部變量。
??資源類型注解,主要作用是約束傳入資源的類型的,指定了該類型就只能傳入該類型的資源進來。
??Typedef注解
??這種類型的注解有兩種:@IntDef和@StringDef。這兩個注解可以用來定義另一個注解,定義時提供一個常量列表,如下所示:
private final static int GET=0;
private final static int POST=1;
private final static int DELETE=2;
private final static int PUT=3;
@IntDef({GET, POST, DELETE,PUT})
@Retention(RetentionPolicy.SOURCE)
public @interface ReqType{}
??上述代碼只拿@IntDef來舉例,定義好的注解@ReqType作用到目標程序元素時,目標程序元素就只能接受GET、POST、DELETE、PUT中的某個值。@StringDef與@IntDef的使用時類似的,這里不再多說。
??Typedef注解的主要作用是,約束目標程序元素接受的值,只允許幾個允許的常量傳給目標程序元素。
??Value Constraints注解
??可以翻譯成“值約束注解”,這種類型的注解有三種@Size、@IntRange和@FloatRange。
- @Size,限制集合或字符串的長度或大小
- @IntRange,限制目標程序元素的取值范圍(整數(shù))
- @FloatRange,限制目標程序元素的取值范圍(浮點數(shù))
??@Size(min=1),表示集合或者字符串的最小長度不能為空;@Size(max=10),表示集合或者字符串的最大長度不能長于10;@Size(5),表示集合或者字符串的長度只能為5;@Size(multiple=2),表示集合或者字符串的長度必須是2的倍數(shù)。另外,括號內的內容,可以組合使用。比如@Size(min = 2, max = 3),表示集合或者字符串的長度最小為2,最大為3.
??@IntRange與@FloatRange的使用方法類似,這里只拿@IntRange舉例。@IntRange(from = 2, to = 10),表示被作用的目標程序元素的取值只能為2-10之間的整數(shù)(包含2,10)。
??@Size的主要作用是,限制目標集合或者字符串的長度或大?。籃IntRange和@FloatRange,限制目標程序元素的取值在一個范圍內。
??線程相關注解
??這類注解總共有4個:@UiThread、@MainThread、@WorkerThread、@BinderThread。這4個注解的作用范圍都是為類、構造方法、方法。它們含義如下:
- @UiThread,要求作用的目標程序元素要運行在UI線程中;
- @MainThread,要求作用的目標程序元素要運行在主線程中;
- @WorkerThread,要求作用的目標程序元素要運行在子線程中;
- @BinderThread,要求作用的目標程序元素要運行在binder線程中。
??經查資料,UI線程和主線程在android上是同一個概念。所以,@UiThread與@MainThread的作用應該是一樣的。@WorkerThread是運行在子線程中,這個好理解,方法或者類要運行在子線程中才可以。@BinderThread,要求方法或類運行在binder線程中。關于binder線程的理解,我們知道app進程用來接收從AMS傳遞過來的信息的ApplicationThread就是運行在Binder線程的,主線程進入無限循環(huán)時,給主線程發(fā)消息喚醒主線程處理消息的很多都是Binder線程。有些方法或者類存在的意義就是為了服務Binder線程,這些方法和類就可以使用注解@BinderThread。
??線程相關的注解就是為了約束程序執(zhí)行在哪個線程的。
??顏色注解
??這類注解有一個:@ColorInt。這個意思是,有時候我們的方法需要的一個顏色值(是一個整型值),而不是資源顏色值,這時候就可用此注解。
??@ColorInt的主要作用就是約束目標程序元素只接收顏色值。
??權限注解
??這類注解有一個:@RequiresPermission。有些方法我們需要調用方取得了某種權限之后才能調用。如下:
@RequiresPermission(Manifest.permission.SET_WALLPAPER)
public abstract void setWallpaper(Bitmap bitmap) throws IOException;
??上面這段代碼的意思是,setWallpaper方法需要調用方取得SET_WALLPAPER權限后才能夠調用。如果被調用方法是要求幾種權限中的至少一種,可以像下面這樣寫
@RequiresPermission(anyOf = {
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION})
public abstract Location getLastKnownLocation(String provider);
??如果被調用方法是要求全部幾種權限,則可以像下面這樣寫
@RequiresPermission(allOf = {
Manifest.permission.READ_HISTORY_BOOKMARKS,
Manifest.permission.WRITE_HISTORY_BOOKMARKS})
public static final void updateVisitedHistory(ContentResolver cr, String url, boolean real) ;
??@RequiresPermission也可以作用到字段上,表示使用此字段需要獲取什么權限。比如Intent常量字符串字段
@RequiresPermission(android.Manifest.permission.BLUETOOTH)
public static final String ACTION_REQUEST_DISCOVERABLE =
"android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
??@RequiresPermission對字段的作用可以進一步擴展,可以做到對此字段的讀寫分別設定相應權限才能訪問。
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
@RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
??權限注解的主要作用是約束程序中的調用方對所調方法、所調字段、所調方法參數(shù)的訪問,有權限才能訪問,無權限不能訪問。
??覆寫方法注解
??這類注解也是有一個:@CallSuper。如果父類的方法需要子類去調用,那可以在父類方法上使用此注解。子類如果沒在覆寫方法中調用父類編譯器就會報錯。
??覆寫方法注解的主要作用是約束子類的方法必須調用父類父方法。
??返回值注解
??返回值注解也是有一個:@CheckResult。你寫了一個方法,你要求調用此方法的調用方必須要使用該方法的返回值,不使用就報錯,那你可以使用此@CheckResult注解。
??返回值注解,主要作用是約束調用方必須使用方法的返回值。
??其他注解
??@Keep,在混淆時,使用此注解的方法應該被保留;@Keep,在混淆時,使用此注解的方法應該被保留;@VisibleForTesting,使類、方法、方法變量擁有更多的可見性,以便于測試。
常見的注解說明
??@Override
??java.lang.Override是一個標記類型注解,它被用作標注方法。它說明了被標注的方法重載了父類的方法,起到了斷言的作用。如果我們使用了這種注解在一個沒有覆蓋父類方法的方法時,java編譯器將以一個編譯錯誤來警示
??@Override,簡單來說,就是用來表明當前的方法是從父類方法中覆寫而來的。
??@Deprecated
??Deprecated也是一種標記類型注解。當一個類型或者類型成員使用@Deprecated修飾的話,編譯器將不鼓勵使用這個被標注的程序元素。所以使用這種修飾具有一定的“延續(xù)性”:如果我們在代碼中通過繼承或者覆蓋的方式使用了這個過時的類型或者成員,雖然繼承或者覆蓋后的類型或者成員并不是被聲明為@Deprecated,但編譯器仍然要報警
??@Deprecated,簡單來說,標注某個方法或字段已經過時了,以后不要再用了。
??@SuppressWarnings
??SuppressWarning不是一個標記類型注解。它有一個類型為String[]的成員,這個成員的值為被禁止的警告名。對于javac編譯器來講,被-Xlint選項有效的警告名也同樣對@SuppressWarings有效,同時編譯器忽略掉無法識別的警告名
@SuppressWarnings("unchecked")
??@SuppressWarnings,簡單來說就是用來屏蔽某些警告的。
??@TargetApi (Build.VERSION_CODES.XX)
??用于屏蔽某一新api中才能使用的方法報的lint檢查出現(xiàn)的錯誤。簡單來說,@TargetApi是給使用了此注解的方法添加訪問權限,跟上面說的@RequiresPermission有點兒類似。
??@SuppressLint(“NewApi”)
??屏蔽一切新api中才能使用的方法報的android lint錯誤。簡單來說,應用中最低sdk版本不能使用的api會報lint錯誤,使用此注解,可以使它不報這個錯誤。
??@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);
??這個用的少,感覺不太重要。
??@Widget
??用于類的注解,表示該類是自定義的Widget類
總結
??一開始,對注解的理解是將元數(shù)據(jù)與程序元素關聯(lián)在一起進行或約束或說明的機制。后來的理解更具體,注解是通過元數(shù)據(jù)定義屬性信息,在使用此注解時給這些屬性信息賦值,然后通過某種方式(比如反射)使用這些屬性信息做操作的機制。
??android本身有注解庫:support annotation,它里面有Nullness注解,可以約束目標程序元素可空或不可為空;有資源類型注解,可以約束目標程序元素只能傳遞某種資源類型的整數(shù);有Typedef注解,可以修飾其它注解,約束目標程序元素只能接受規(guī)定的幾個整數(shù)或字符串中的某一個值;有Value Constraints注解,可以約束目標集合或目標字符串的長度,或約束目標程序元素只能接受在一個整數(shù)范圍或浮點數(shù)范圍內的值;有線程相關注解,可以約束目標程序元素只能運行在主線程、子線程或binder線程內;有權限注解,可以約束調用目標程序元素的調用方要具備某種權限才能進行調用訪問;有覆寫方法注解,要求子類覆寫方法必須在其方法內調用父類的該父方法;有返回值注解,要求調用某方法的調用方必須使用該方法的返回值等等。
參考資料
??Java學習之注解Annotation實現(xiàn)原理
??深入理解Java:注解(Annotation)自定義注解入門
??使用Android Support Annotations優(yōu)化你的代碼
??Android Support庫——support annotations