你不知道的 Java 注解那些事!
戳上方藍(lán)字 “程序猿雜貨鋪” 關(guān)注我 并?置頂星標(biāo)!
你的關(guān)注意義重大!
原文 |?http://1t.click/JpS
注解對(duì)于開(kāi)發(fā)人員來(lái)講既熟悉又陌生,熟悉是因?yàn)橹灰闶亲鲩_(kāi)發(fā),都會(huì)用到注解(常見(jiàn)的 @Override),陌生是因?yàn)榧词共皇褂米⒔庖舱粘D軌蜻M(jìn)行開(kāi)發(fā),注解不是必須的,但了解注解有助于我們深入理解某些第三方框架(比如 Android Support Annotations、JUnit、xUtils、ActiveAndroid 等),提高工作效率。
Java 注解又稱為標(biāo)注,是 Java 從 1.5 開(kāi)始支持加入源碼的特殊語(yǔ)法元數(shù)據(jù):Java中的類、方法、變量、參數(shù)、包都可以被注解。這里提到的元數(shù)據(jù)是描述數(shù)據(jù)的數(shù)據(jù),結(jié)合實(shí)例來(lái)說(shuō)明:
name="app_name">AnnotionDemo
這里的 "app_name" 就是描述數(shù)據(jù) "AnnotionDemo" 的數(shù)據(jù),這是在配置文件中寫(xiě)的,注解是在源碼中寫(xiě)的,如下所示:
@Overrideprotectedvoid onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main_layout);newThread(newRunnable(){@Overridepublicvoid run(){setTextInOtherThread();}}).start();}
在上面的代碼中,在 MainActivity.java 中復(fù)寫(xiě)了父類 Activity.java 的 onCreate 方法,使用到了 @Override 注解。但即使不加上 @Override 注解標(biāo)記代碼,程序也能夠正常運(yùn)行。那這里的 @Override 注解有什么用呢?使用它有什么好處?事實(shí)上,@Override 是告訴編譯器這個(gè)方法是一個(gè)重寫(xiě)方法,如果父類中不存在該方法,編譯器會(huì)報(bào)錯(cuò),提示該方法不是父類中的方法。如果不小心拼寫(xiě)錯(cuò)誤,將 onCreate 寫(xiě)成了 onCreat,而且沒(méi)有使用@Override 注解,程序依然能夠編譯通過(guò),但運(yùn)行結(jié)果和期望的大不相同。從示例可以看出,注解有助于閱讀代碼。
使用注解很簡(jiǎn)單,根據(jù)注解類的 @Target 所修飾的對(duì)象范圍,可以在類、方法、變量、參數(shù)、包中使用 “@+注解類名 + [屬性值]” 的方式使用注解。比如:
@UiThreadprivatevoid setTextInOtherThread(@StringResint resId){TextView threadTxtView =(TextView)MainActivity.this.findViewById(R.id.threadTxtViewId);threadTxtView.setText(resId);}
特別說(shuō)明:
注解僅僅是元數(shù)據(jù),和業(yè)務(wù)邏輯無(wú)關(guān),所以當(dāng)你查看注解類時(shí),發(fā)現(xiàn)里面沒(méi)有任何邏輯處理;
javadoc 中的 @author、@version、@param、@return、@deprecated、@hide、@throws、@exception、@see 是標(biāo)記,并不是注解;
注解的作用
格式檢查:告訴編譯器信息,比如被 @Override 標(biāo)記的方法如果不是父類的某個(gè)方法,IDE 會(huì)報(bào)錯(cuò);
減少配置:運(yùn)行時(shí)動(dòng)態(tài)處理,得到注解信息,實(shí)現(xiàn)代替配置文件的功能;
減少重復(fù)工作:比如第三方框架 xUtils,通過(guò)注解 @ViewInject 減少對(duì) findViewById 的調(diào)用,類似的還有(JUnit、ActiveAndroid 等);
注解是如何工作的?
注解僅僅是元數(shù)據(jù),和業(yè)務(wù)邏輯無(wú)關(guān),所以當(dāng)你查看注解類時(shí),發(fā)現(xiàn)里面沒(méi)有任何邏輯處理,eg:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public@interfaceViewInject{int value();/* parent view id */int parentId()default0;}
如果注解不包含業(yè)務(wù)邏輯處理,必然有人來(lái)實(shí)現(xiàn)這些邏輯。注解的邏輯實(shí)現(xiàn)是元數(shù)據(jù)的用戶來(lái)處理的,注解僅僅提供它定義的屬性(類/方法/變量/參數(shù)/包)的信息,注解的用戶來(lái)讀取這些信息并實(shí)現(xiàn)必要的邏輯。當(dāng)使用java中的注解時(shí)(比如 @Override、@Deprecated、@SuppressWarnings)JVM 就是用戶,它在字節(jié)碼層面工作。如果是自定義的注解,比如第三方框架ActiveAndroid,它的用戶是每個(gè)使用注解的類,所有使用注解的類都需要繼承 Model.java,在 Model.java 的構(gòu)造方法中通過(guò)反射來(lái)獲取注解類中的每個(gè)屬性:
publicTableInfo(ClassextendsModel> type){mType = type;finalTable tableAnnotation = type.getAnnotation(Table.class);if(tableAnnotation !=null){mTableName = tableAnnotation.name();mIdName = tableAnnotation.id();}else{mTableName = type.getSimpleName();}// Manually add the id column since it is not declared like the other columns.Field idField = getIdField(type);mColumnNames.put(idField, mIdName);List<Field> fields =newLinkedList<Field>(ReflectionUtils.getDeclaredColumnFields(type));Collections.reverse(fields);for(Field field : fields){if(field.isAnnotationPresent(Column.class)){finalColumn columnAnnotation = field.getAnnotation(Column.class);String columnName = columnAnnotation.name();if(TextUtils.isEmpty(columnName)){columnName = field.getName();}mColumnNames.put(field, columnName);}}}
注解和配置文件的區(qū)別
通過(guò)上面的描述可以發(fā)現(xiàn),其實(shí)注解干的很多事情,通過(guò)配置文件也可以干,比如為類設(shè)置配置屬性;但注解和配置文件是有很多區(qū)別的,在實(shí)際編程過(guò)程中,注解和配置文件配合使用在工作效率、低耦合、可拓展性方面才會(huì)達(dá)到權(quán)衡。
配置文件:
使用場(chǎng)合:
外部依賴的配置,比如 build.gradle 中的依賴配置;
同一項(xiàng)目團(tuán)隊(duì)內(nèi)部達(dá)成一致的時(shí)候;
非代碼類的資源文件(比如圖片、布局、數(shù)據(jù)、簽名文件等);
優(yōu)點(diǎn):
降低耦合,配置集中,容易擴(kuò)展,比如 Android 應(yīng)用多語(yǔ)言支持;
對(duì)象之間的關(guān)系一目了然,比如 strings.xml;
xml 配置文件比注解功能齊全,支持的類型更多,比如 drawable、style等;
缺點(diǎn):
繁瑣;
類型不安全,比如 R.java 中的都是資源 ID,用 TextView 的 setText 方法時(shí)傳入 int 值時(shí)無(wú)法檢測(cè)出該值是否為資源 ID,但 @StringRes 可以;
注解:
使用場(chǎng)合:
動(dòng)態(tài)配置信息;
代為實(shí)現(xiàn)程序邏輯(比如 xUtils 中的 @ViewInject 代為實(shí)現(xiàn) findViewById);
代碼格式檢查,比如 Override、Deprecated、NonNull、StringRes 等,便于 IDE 能夠檢查出代碼錯(cuò)誤;
優(yōu)點(diǎn):
在 class 文件中,提高程序的內(nèi)聚性;
減少重復(fù)工作,提高開(kāi)發(fā)效率,比如 findViewById。
缺點(diǎn):
如果對(duì) annotation 進(jìn)行修改,需要重新編譯整個(gè)工程;
業(yè)務(wù)類之間的關(guān)系不如 XML 配置那樣一目了然;
程序中過(guò)多的 annotation,對(duì)于代碼的簡(jiǎn)潔度有一定影響;
擴(kuò)展性較差;
自定義注解
通過(guò)閱讀注解類的源碼可以發(fā)現(xiàn),任何一個(gè)注解類都有如下特征:
注解類會(huì)被 @interface 標(biāo)記;
注解類的頂部會(huì)被 @Documented、@Retention、@Target、@Inherited 這四個(gè)注解標(biāo)記(@Documented、@Inherited可選,@Retention、@Target必須要有);
@UiThread 源碼:
@Documented@Retention(CLASS)@Target({METHOD,CONSTRUCTOR,TYPE})public@interfaceUiThread{}
上文提到的四個(gè)注解:@Documented、@Retention、@Target、@Inherited就是元注解,它們的作用是負(fù)責(zé)注解其它注解,主要是描述注解的一些屬性,任何注解都離不開(kāi)元注解(包括元注解自身,通過(guò)元注解可以自定義注解),元注解的用戶是 JDK,JDK 已經(jīng)幫助我們實(shí)現(xiàn)了這四個(gè)注解的邏輯。這四個(gè)注解在 JDK 的java.lang.annotation 包中。對(duì)每個(gè)元注解的詳細(xì)說(shuō)明如下:
@Target
作用:用于描述注解的使用范圍,即被描述的注解可以用在什么地方;
取值:
1、CONSTRUCTOR:構(gòu)造器;
2、FIELD:實(shí)例;
3、LOCAL_VARIABLE:局部變量;
4、METHOD:方法;
5、PACKAGE:包;
6、PARAMETER:參數(shù);
7、TYPE:類、接口(包括注解類型) 或enum聲明。
示例:
/***** 實(shí)體注解接口*/@Target(value ={ElementType.TYPE})@Retention(value =RetentionPolicy.RUNTIME)public@interfaceEntity{/**** 實(shí)體默認(rèn)firstLevelCache屬性為false* @return boolean*/boolean firstLevelCache()defaultfalse;/**** 實(shí)體默認(rèn)secondLevelCache屬性為false* @return boolean*/boolean secondLevelCache()defaulttrue;/**** 表名默認(rèn)為空* @return String*/String tableName()default"";/**** 默認(rèn)以""分割注解*/String split()default"";}
@Retention
作用:表示需要在什么級(jí)別保存該注解信息,用于描述注解的生命周期,即被描述的注解在什么范圍內(nèi)有效;
取值:
1、SOURCE:在源文件中有效,即源文件保留;
2、CLASS:在class文件中有效,即class保留;
3、RUNTIME:在運(yùn)行時(shí)有效,即運(yùn)行時(shí)保留;
示例:
/**** 字段注解接口*/@Target(value ={ElementType.FIELD})//注解可以被添加在實(shí)例上@Retention(value =RetentionPolicy.RUNTIME)//注解保存在JVM運(yùn)行時(shí)刻,能夠在運(yùn)行時(shí)刻通過(guò)反射API來(lái)獲取到注解的信息public@interfaceColumn{String name();//注解的name屬性}
@Documented
作用:用于描述其它類型的 annotation 應(yīng)該被作為被標(biāo)注的程序成員的公共 API,因此可以被例如 javadoc 此類的工具文檔化。
取值:它屬于標(biāo)記注解,沒(méi)有成員;
示例:
@Documented@Retention(CLASS)@Target({METHOD,CONSTRUCTOR,TYPE})public@interfaceUiThread{}
@Inherited
作用:用于描述某個(gè)被標(biāo)注的類型是可被繼承的。如果一個(gè)使用了 @Inherited 修飾的 annotation 類型被用于一個(gè) class,則這個(gè) annotation 將被用于該class的子類。
取值:它屬于標(biāo)記注解,沒(méi)有成員;
示例:
/*** @author wangsheng**/@Inheritedpublic@interfaceGreeting{publicenumFontColor{ BULE,RED,GREEN};String name();FontColor fontColor()defaultFontColor.GREEN;}
使用 @interface 自定義注解時(shí),自動(dòng)繼承了 java.lang.annotation.Annotation 接口,由編譯程序自動(dòng)完成其他細(xì)節(jié)。在定義注解時(shí),不能繼承其他的注解或接口。@interface 用來(lái)聲明一個(gè)注解,其中的每一個(gè)方法實(shí)際上是聲明了一個(gè)配置參數(shù)。方法的名稱就是參數(shù)的名稱,返回值類型就是參數(shù)的類型(返回值類型只能是基本類型、Class、String、enum)。可以通過(guò) default 來(lái)聲明參數(shù)的默認(rèn)值。
自定義注解格式
元注解public@interface注解名{定義體;}
注解參數(shù)可支持的數(shù)據(jù)類型
1、所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short);
2、String 類型;
3、Class 類型;
4、enum 類型;
5、Annotation 類型;
6、以上所有類型的數(shù)組。
特別說(shuō)明:
1、注解類中的方法只能用 public 或者默認(rèn)這兩個(gè)訪問(wèn)權(quán)修飾,不寫(xiě) public 就是默認(rèn),eg:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceFruitColor{publicenumColor{ BULE,RED,GREEN};Color fruitColor()defaultColor.GREEN;}
2、如果注解類中只有一個(gè)成員,最好把方法名設(shè)置為"value",比如:
@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceFruitName{String value()default"";}
3、注解元素必須有確定的值,要么在定義注解的默認(rèn)值中指定,要么在使用注解時(shí)指定,非基本類型的注解元素的值不可為 null。因此, 使用空字符串或0作為默認(rèn)值是一種常用的做法。
實(shí)例演示
ToDo.java:注解類
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@interfaceTodo{publicenumPriority{LOW, MEDIUM, HIGH}publicenumStatus{STARTED, NOT_STARTED}String author()default"Yash";Priority priority()defaultPriority.LOW;Status status()defaultStatus.NOT_STARTED;}
BusinessLogic:使用注解的類
publicclassBusinessLogic{publicBusinessLogic(){super();}publicvoid compltedMethod(){System.out.println("This method is complete");}@Todo(priority =Todo.Priority.HIGH)publicvoid notYetStartedMethod(){// No Code Written yet}@Todo(priority =Todo.Priority.MEDIUM, author ="Uday", status =Todo.Status.STARTED)publicvoid incompleteMethod1(){//Some business logic is written//But its not complete yet}@Todo(priority =Todo.Priority.LOW, status =Todo.Status.STARTED )publicvoid incompleteMethod2(){//Some business logic is written//But its not complete yet}}
TodoReport.java:解析注解信息
publicclassTodoReport{publicTodoReport(){super();}publicstaticvoid main(String[] args){getTodoReportForBusinessLogic();}/*** 解析使用注解的類,獲取通過(guò)注解設(shè)置的屬性*/privatestaticvoid getTodoReportForBusinessLogic(){Class businessLogicClass =BusinessLogic.class;for(Method method : businessLogicClass.getMethods()){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());System.out.println(" --------------------------- ");}}}}
執(zhí)行結(jié)果如下圖所示:

推薦閱讀
如果這篇文章對(duì)你有幫助
請(qǐng)幫忙轉(zhuǎn)發(fā),點(diǎn)個(gè)在看



