注解(一)- 基础知识与运行时注解
在java和android 中,注解的運(yùn)用非常廣泛,很多的類庫,第三方框架中都用到了注解。所以我們有必要來熟悉注解的相關(guān)知識。
Annotation,注解(也稱為元數(shù)據(jù)),可以為我們在代碼中添加額外的信息,我們也可以很方便的使用這些數(shù)據(jù)
當(dāng)然,在代碼中添加額外信息我們最經(jīng)常使用的是注釋(comment),好的注釋對于理解代碼或邏輯是非常重要的,comment通俗易懂,并且使用一些工具,注釋也可以生成專門的文檔,但是注解相比注釋,擁有更加強(qiáng)大的,不可替代的功能。它可以提供編譯期的一些操作,比如類型檢查,生成新的文件(包括java文件等)。
annotation是java5才引入的新特性,它通過將信息和源代碼結(jié)合在一起,可以提供一些java語言本身無法的表達(dá)的額外信息。它的相關(guān)內(nèi)容可以由編譯器來測試或者驗(yàn)證,當(dāng)然,運(yùn)行時(shí)期也可以使用注解來提供額外信息。
注解的語法其實(shí)比較簡單,除了多個(gè)@符號,其他和java本身語法一樣。java 5 在java.lang中內(nèi)置了三種標(biāo)準(zhǔn)注解。(android中內(nèi)置的注解更多)。
- @Override: 表示當(dāng)前方法其實(shí)是覆蓋父類的方法,如果有拼寫錯(cuò)誤等,編譯器(和IDE)就可以發(fā)出錯(cuò)誤提示。
- @Deprecated:表示該類或方法不建議使用了,未來有可能被廢棄或者被移除,如果程序員使用了該類或方法,那么編譯器(和IDE)將會發(fā)出警告信息。 這里也就就是warning而已,你也可以繼續(xù)使用,不過建議還是不要使用被Deprecated的API,說不定未來哪個(gè)版本就被移除了。
- @SuppressWarnings:給編譯器一條指令,告訴它對范圍內(nèi)的某些類型的警告保持靜默。這樣編譯時(shí)就不再輸出該警告了。
看一個(gè)簡單的demo:
從截圖看到看到,eclipse中對不同類型的annotation,都做了相應(yīng)的提示。
并且也可以看到同一個(gè)元素上也可以使用多個(gè)不同的注解。而SuppressWarnings注解接收的其實(shí)是一個(gè)數(shù)組,例如demo當(dāng)中的 unchecked,表示是未檢查的轉(zhuǎn)換時(shí)的警告,而fallthrough則表示在switch塊中,某個(gè)case沒有使用break,而直接流向了下一條case時(shí)的警告。從編碼習(xí)慣上來講,你屏蔽了fallthrough的警告,也可以告別其他人,case沒有使用break,這是因?yàn)榇a的邏輯,而不是你忘記寫了。
自定義注解
從以上demo就可以看出,注解很有用,也方便,但是內(nèi)置的注解不可能滿足我們五花八門的需求,所以此時(shí)就需要我們來自定義注解了。
java中內(nèi)置了四種元注解(meta-annotation),元注解就是負(fù)責(zé)注解其他注解(這句話好繞啊),來幫助我們自定義注解的。
- @Target : 表示該注解可以用在什么地方(使用范圍),可能的ElementType參數(shù)包括:
- CONSTRUCTOR:構(gòu)造器的聲明。
- FIELD:域聲明(包含enum的實(shí)例)。
- LOCAL_VARIABLE : 局部變量聲明。
- METHOD : 方法聲明。
- PACKAGE: 包聲明。
- PARAMETER: 參數(shù)聲明。
- TYPE: 類,接口(包括注解類型)或 enum類型。
- @Retention: 表示需要在什么級別保存該注解信息(生命周期),可選的RetentionPolicy參數(shù)包括:
- SOURCE:源碼級別,注解將被編譯器丟棄。
- CLASS: 在編譯器生成的class文件中可用,但是會被VM丟棄。(默認(rèn)的就是該級別)。
- RUNTIME: 運(yùn)行期保留該注解,所以此時(shí)可以通過反射機(jī)制來讀取注解的信息。
- @Documented:將注解包含在javadoc中。
- @Inherited:允許子類繼承父類的注解。
具體的關(guān)于如何定義自己的注解,幾行簡單的代碼勝過千言萬語。
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface UseCase {public int id();public String description() default "no description"; } public class PasswordUtils {@UseCase(id = 47, description = "password must contain one number")public boolean validatePassword(String password){return true;}@UseCase(id = 48)public String encryptPassword(String password){return "encryptPassword()";}@UseCase(id = 49, description = "new password can't equal previously used ones")public boolean checkouForNewPassword(){return false;} }通過代碼可以看出,定義一個(gè)注解和定義一個(gè)interface非常相似,只不過多了一個(gè)@符號罷了。在定義注解時(shí),使用了一些元注解,比如@Target或者 @Retention,它們分別用來表明注解的應(yīng)用范圍,生命周期情況。
而從語法的角度來看,注解的使用方法,也和public,static或void等修飾符一樣,沒什么大的差別。
而定義注解時(shí),可以看到定義體里面包含了一些比較特殊的方法。我們也稱之為配置參數(shù),這些方法只能是public或者default訪問權(quán)限,方法的返回值就是配置參數(shù)的類型,并且我們可以為其指定默認(rèn)值,我們在分析處理注解時(shí),程序或工具就可以利用這些值。
@UseCase由UseCase.java定義,并且其中包含了兩個(gè)配置參數(shù)id和description,并且它們都是有類型的,配置參數(shù)可以使用的類型如下:
- 所有的基本類型。(int,float,boolean等)。
- String
- Class
- enum
- Annotation
- 以上類型的數(shù)組
關(guān)于注解元素的問題,我們還需要注意如下幾個(gè)方面。
而對于上面我們那個(gè) UseCase的demo,我們寫一個(gè)程序來進(jìn)行處理。
public class UseCaseTracker {public static void main(String[] args) {// TODO Auto-generated method stubList<Integer> useCases = new ArrayList<Integer>();Collections.addAll(useCases, 47, 48, 49, 50);trackUseCases(useCases, PasswordUtils.class);}public static void trackUseCases(List<Integer> useCases, Class<?> cl) {for (Method m : cl.getDeclaredMethods()){// getDeclaredAnnotation 返回指定類型的 注解對象 UseCase uc = m.getDeclaredAnnotation(UseCase.class);if (uc != null){System.out.println("Found UseCase :" + uc.id() + "\t" + uc.description());useCases.remove(new Integer(uc.id()));}}System.out.println("--------------------------------------");for (int i : useCases){System.out.println("Warning : Miss use case --" + i);}} }輸出結(jié)果為:
Found UseCase :47 password must contain one number Found UseCase :48 no description Found UseCase :49 new password can't equal previously used ones -------------------------------------- Warning : Miss use case --50通過上面代碼我們可以看出,利用反射,我們可以很好的處理注解,這其實(shí)就像處理普通類那樣。
注解的基本知識本身不復(fù)雜,那么下面我們看一個(gè)復(fù)雜點(diǎn)的例子。在android常用的xutils3框架中,包含了一個(gè)數(shù)據(jù)庫模塊,這個(gè)數(shù)據(jù)庫在使用時(shí),就是通過注解來創(chuàng)造表名或列名的,那么下面,我們也自定義一個(gè)注解,通過注解來生成javaBean對象的創(chuàng)建數(shù)據(jù)庫表的語句。
//告訴注解處理器,需要生成一個(gè)數(shù)據(jù)庫表 //這個(gè)注解只能用于類,接口,enum @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface DBTable {public String name() default ""; } @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface Constraints {//這些元素都有默認(rèn)值,這樣我們就不必強(qiáng)迫程序員必須賦值了boolean primaryKey() default false;boolean allowNull() default true;boolean unique() default false; } /*** @author www.yaoxiaowen.com*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLString {//定義了元素名為value,在符合條件時(shí),我們使用時(shí),可以直接在括號內(nèi)輸入value的值就ok了。int value() default 0;String name() default "";Constraints constraints() default @Constraints; } /*** SQLInteger 和 SQLString一樣,都是要求在javabean上,根據(jù)不同的數(shù)據(jù)類型使用不同的注解* @author www.yaoxiaowen.com*/ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SQLInteger {String name() default "";//注解嵌套Constraints constraints() default @Constraints; } /*** 我們的目的就是為該javabean生成一個(gè)創(chuàng)建表的語句* @author www.yaoxiaowem.com*/ @DBTable(name = "MEMBER") public class Member {@SQLString(30)String firstName;@SQLString(50)String lastName;@SQLIntegerInteger age;@SQLString(value = 30,constraints = @Constraints(primaryKey = true))String handle;static int memberCount;} public class TableCreator {public static void main(String[] args) throws Exception{StringBuilder createCommand = new StringBuilder();String className = "test.annotation.database.Member";Class<?> cl = Class.forName(className);DBTable dbTable = cl.getAnnotation(DBTable.class);String tableName = dbTable.name();//如果名字為空,就使用類名if (tableName.length() < 1){tableName = cl.getName().toUpperCase();}createCommand.append("CREATE TABLE " + tableName + "(");List<String> columnDefs = new ArrayList<String>();for (Field field : cl.getDeclaredFields()){String columnName = null;Annotation[] anns = field.getDeclaredAnnotations();if (anns.length < 1){continue;}//這里的寫法之所以簡單,因?yàn)槲覀兠總€(gè)Field上面最多只有一個(gè) 注解if (anns[0] instanceof SQLInteger){SQLInteger sInt = (SQLInteger)anns[0];//沒有名字的話,我們就使用Field的名字來做為 列名if (sInt.name().length() < 1){columnName = field.getName();}else {columnName = sInt.name();}columnDefs.add(columnName + " INT " + getConstraints(sInt.constraints()));}if (anns[0] instanceof SQLString){SQLString sString = (SQLString)anns[0];if (sString.name().length() < 1){columnName = field.getName();}else {columnName = sString.name();}columnDefs.add(columnName + " VARCHAR(" + sString.value() + ")" + getConstraints(sString.constraints()));}}for (String columnDef : columnDefs){createCommand.append("\n\t" + columnDef + ",");}//移除最后的一個(gè)逗號String tableCreate = createCommand.substring(0, createCommand.length()-1) + ");";System.out.println("TABLE Creation SQL for " + className + " is: \n" + tableCreate);}//解析出 Constraints 注解的內(nèi)容private static String getConstraints(Constraints con){String constraints = "";if (!con.allowNull()){constraints += " Not Null";}if (con.primaryKey()){constraints += " PRIMARY KEY ";}if (con.unique()){constraints += " NNIQUE ";}return constraints;} }輸出結(jié)果為:
TABLE Creation SQL for test.annotation.database.Member is: CREATE TABLE MEMBER(firstName VARCHAR(30),lastName VARCHAR(50),age INT ,handle VARCHAR(30) PRIMARY KEY );這個(gè)demo雖然沒有實(shí)際的意義,但是仔細(xì)分析該demo對于我們理解注解還是比較有幫助的。
在java 5當(dāng)中引入 annotation時(shí),java也引入了注解處理工具Annotation Processing Tool (apt),apt是一個(gè)可以在編譯時(shí)使用的命令行工具,但是它是Oracle提供的私有實(shí)現(xiàn),所以該工具在java 8中被移除了,而在java6中,通過 JSR 269 annotation processing facility來規(guī)范了自定義注解處理器的這一功能。也有了新的API(javax.annotation.processing),而關(guān)于這些內(nèi)容,我們下一個(gè)篇文章再進(jìn)行介紹。
作者: www.yaoxiaowen.com
github: https://github.com/yaowen369
歡迎對于本人的博客內(nèi)容批評指點(diǎn),如果問題,可評論或郵件(yaowen369@gmail.com)聯(lián)系
歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明出處.謝謝
轉(zhuǎn)載于:https://www.cnblogs.com/yaoxiaowen/p/6750192.html
總結(jié)
以上是生活随笔為你收集整理的注解(一)- 基础知识与运行时注解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos安装Oracle virtu
- 下一篇: iOS 配置pch文件