使用Java注解來簡(jiǎn)化你的代碼

?????注解(Annotation)就是一種標(biāo)簽给梅,可以插入到源代碼中,我們的編譯器可以對(duì)他們進(jìn)行邏輯判斷飒筑,或者我們可以自己寫一個(gè)工具方法來讀取我們?cè)创a中的注解信息离例,從而實(shí)現(xiàn)某種操作。需要申明一點(diǎn)凡怎,注解不會(huì)改變編譯器的編譯方式校焦,也不會(huì)改變虛擬機(jī)指令執(zhí)行的順序,它更可以理解為是一種特殊的注釋统倒,本身不會(huì)起到任何作用寨典,需要工具方法或者編譯器本身讀取注解的內(nèi)容繼而控制進(jìn)行某種操作。本篇文章將從以下幾點(diǎn)詳細(xì)的介紹下Java注解的使用:

  • 元數(shù)據(jù)和注解(Annotation)
  • 按照參數(shù)個(gè)數(shù)分類注解(標(biāo)記房匆,單值耸成,完整)
  • 按照注解使用途徑分類(標(biāo)準(zhǔn),元注解浴鸿,自定義)
  • 自定義注解處理器完成讀取注解內(nèi)容的操作

一井氢、元數(shù)據(jù)和注解
?????元數(shù)據(jù)(meta-data)就是指用來描述數(shù)據(jù)的數(shù)據(jù),它往往是以標(biāo)簽的形式出現(xiàn)赚楚,主要用于描述代碼塊之間的聯(lián)系毙沾。我們的注解就是一種元數(shù)據(jù),根據(jù)它所起到的作用宠页,我們可以大致將它分為以下三類:

  • 編寫文檔:通過代碼中標(biāo)識(shí)的元數(shù)據(jù)生成文檔
  • 代碼分析:通過代碼中的元數(shù)據(jù)獲取其中信息內(nèi)容
  • 編譯檢查:通過標(biāo)記注解可以完成對(duì)代碼塊的檢查左胞,例如:@Override,用于檢查格式

二举户、標(biāo)準(zhǔn)注解(系統(tǒng)自帶)
?????在我們jdk的java.lang包中定義了三個(gè)注解烤宙,他們是:@Override,@Deprecated俭嘁,@SuppressWarnnings躺枕。Override這個(gè)注解我們經(jīng)常會(huì)使用到,在子類重寫父類方法的時(shí)候就會(huì)使用到,他會(huì)幫助我們校驗(yàn)格式拐云,確保我們正在定義的方法是在重寫了父類的對(duì)應(yīng)方法罢猪。Deprecated注解一般修飾在類或者方法之前,用于表示該方法或者類已經(jīng)不再推薦使用了叉瘩。SuppressWarnnings注解主要用于抑制編譯器警告膳帕,具體的我們簡(jiǎn)單的演示下。

public class People {

    public void sayHello(){
        System.out.println("helo walker");
    }
}

public class Student extends People {

    /*@Override
    public void sayHello(){
        System.out.println("hello yam");
    }這樣是沒有問題的*/

    @Override
    public void say(){
        System.out.println("hello yam");
    }/*如果你定義的方法不能重寫父類某個(gè)方法薇缅,要么拼寫錯(cuò)誤危彩,參數(shù)個(gè)數(shù),方法名不一樣等泳桦,編譯拋出警告*/
}

我們需要注意的是汤徽,這里的override注解只能用于修飾方法,不能用于修飾類或者域灸撰。

public class Student extends People {

    @Deprecated
    public void say(){
        System.out.println("hello yam");
    }
}
//調(diào)用過時(shí)方法
public static void main(String[] args){
        Student s = new Student();
        s.say();
    }
這里寫圖片描述

這里寫圖片描述

雖然編譯時(shí)拋出了警告谒府,但是程序依然可以正常的運(yùn)行結(jié)束。此注解只是告知用戶被標(biāo)記的方法或者類已經(jīng)不再推薦使用梧奢,但是你依然是可以使用的狱掂。之所以建議不再使用,一定是有了更好的取代物了亲轨,如果你一定要在你的項(xiàng)目中使用趋惨,等待新的jdk版本發(fā)布之后,很可能刪除了這些方法或者類惦蚊,可能會(huì)導(dǎo)致你的項(xiàng)目原先的一些方法或者類無法識(shí)別器虾。

@SuppressWarnings("deprecation")
    public static void main(String[] args){
        Student s = new Student();
        s.say();
    }

例如,我們可以使用SuppressWarnings注解蹦锋,阻止彈出過時(shí)警告兆沙。關(guān)于SuppressWarnings的參數(shù)主要有以下幾種:

  • deprecation:使用了不贊成使用的類或方法時(shí)的警告
  • unchecked:執(zhí)行了未檢查的轉(zhuǎn)換時(shí)的警告,例如當(dāng)使用集合時(shí)沒有用泛型 (Generics) 來指定集合保存的類型;
  • fallthrough:當(dāng) Switch 程序塊直接通往下一種情況而沒有 Break 時(shí)的警告;
  • path:在類路徑莉掂、源文件路徑等中有不存在的路徑時(shí)的警告;
  • serial:當(dāng)在可序列化的類上缺少 serialVersionUID 定義時(shí)的警告;
  • finally:任何 finally 子句不能正常完成時(shí)的警告;
  • all:關(guān)于以上所有情況的警告葛圃。

三、元注解
?????元注解就是用來注解注解的注解憎妙。定義可能有點(diǎn)繞库正,其實(shí)元注解是一種注解,他可以加在一般的注解上用于限制該注解的使用范圍厘唾,生命周期等褥符。一般在自定義注解時(shí)候使用的多。在jdk的中java.lang.annotation包中定義了四個(gè)元注解:

  • @Target:指定被修飾的注解的作用范圍
  • @Retention:指定了被修飾的注解的生命周期
  • @Documented:指定了被修飾的注解是可以被例如Javadoc等工具文檔化的
  • @Inherited:指定了被修飾的注解修飾程序元素的時(shí)候是可以被子類繼承的

我們首先看看@Target的使用:

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

}

這是系統(tǒng)注解Override的定義源代碼抚垃,我們看到Target注解中參數(shù)ElementType.METHOD表示該注解只能用于修飾方法喷楣。使用Target注解限定了Override的修飾范圍只能使方法趟大,不能是類或者域。Target還有一些其他的參數(shù):

  • CONSTRUCTOR:用于描述構(gòu)造器
  • FIELD:用于描述域
  • LOCAL_VARIABLE:用于描述局部變量
  • METHOD:用于描述方法
  • PACKAGE:用于描述包
  • PARAMETER:用于描述參數(shù)
  • TYPE:用于描述類铣焊、接口(包括注解類型) 或enum聲明

通過上述的參數(shù)我們可以在定義一個(gè)注解的時(shí)候限定他的作用范圍逊朽。

下面看看Retention這個(gè)元注解,依然以注解Override為例曲伊,

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

我們看到Retention中使用了參數(shù)RetentionPolicy.SOURCE惋耙,這個(gè)參數(shù)表示該注解只在源代碼中有效,進(jìn)過編譯之后將會(huì)被丟棄熊昌。還有一些其他參數(shù):

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在class文件中有效(即class保留)
  • RUNTIME:在運(yùn)行時(shí)有效(即運(yùn)行時(shí)保留)
        
    SOURCE表示編譯器編譯之后的class文件中是不存在這一行注解代碼的,CLASS范圍表示編譯器編譯之后湿酸,注解代碼存在于class文件中婿屹,但是jvm在加載此class文件的時(shí)候會(huì)自動(dòng)忽略掉這一行注解代碼。RUNTIME表示jvm加載class文件的時(shí)候會(huì)被讀取到內(nèi)存推溃,也就是運(yùn)行時(shí)保留昂利。

接著使注解Documented,這是一個(gè)關(guān)于文檔的元注解铁坎,被它注解的注解在注解其他方法或者類的時(shí)候可以被Javadoc等工具文檔化蜂奸,對(duì)于一般的注解,在Javadoc等工具文檔化類或者方法的時(shí)候會(huì)丟棄注解內(nèi)容硬萍,使用它就可以使得文檔化的時(shí)候依然保存著注解代碼扩所。

//Test是一個(gè)被元注解Documented修飾
public class User {
    @Test(value = 10,description = "do something")
    public void test1() {
    }
}

使用Javadoc生成API:


這里寫圖片描述

類User中的方法test1方法的頭部是保留著注解的,如果是一般的注解則不會(huì)保留朴乖。

最后是元注解Inherited祖屏,我們知道如果一個(gè)普通的注解修飾了一個(gè)父類,那么他的子類是不能繼承修飾父類的注解的买羞。

@Deprecated
public class People {

    public void sayHello(){
        System.out.println("helo walker");
    }
}

public class Student extends People {
    public void say(){
        System.out.println("hello yam");
    }
}

我們可以看到袁勺,在父類people上使用了注解Deprecated,people類名上是有刪除線的(粘貼到此處并沒有顯示)表示此類不推薦使用畜普,但是我們可以看到在子類Student上是沒有刪除線的期丰,也就是父類廢棄了,子類依然是正常的吃挑。(注解不會(huì)被繼承)钝荡,但是如果我們希望子類能夠繼承父類的某些注解,那么只需要在定義該注解的時(shí)候使用我們的元注解Inherited修飾即可儒鹿。

四化撕、自定義注解
?????以上我們看到的標(biāo)準(zhǔn)注解,元注解都是jdk中定義好了的约炎,如果我們想要自定義一個(gè)自己的注解就需要通過@interface來定義一個(gè)全新的注解植阴。

//定義一個(gè)注解
public @interface myAnnotion {
    
}

使用@interface定義一個(gè)注解的時(shí)候蟹瘾,會(huì)自動(dòng)繼承java.lang.annotation.Annotation接口,以下是其中的一些方法:

boolean equals(Object obj);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();

我們自定義的注解掠手,除了多了個(gè)@符號(hào)憾朴,其他的和定義一個(gè)接口是一樣的,所以這些方法我們不用實(shí)現(xiàn)喷鸽。以上我們定義的是一個(gè)沒有注解體的一個(gè)注解众雷,像這樣的注解我們叫做標(biāo)記注解,這是表示一種標(biāo)記做祝,編譯器根據(jù)某個(gè)類或方法是否具有此標(biāo)記來判斷是否要添加一些代碼或做一定的檢測(cè)操作砾省。例如:@Override注解就是一個(gè)標(biāo)記注解,如果某個(gè)方法前被修飾了此注解混槐,編譯器在編譯時(shí)會(huì)找到父類编兄,判斷對(duì)應(yīng)的方法是否完成了重寫的格式。
?????下面聲明了一個(gè)具有注解體的注解:

public @interface myAnnotion {

    String name() default "";   
    int age();
}

我們說過声登,聲明注解和聲明接口很是類似狠鸳,所以注解中的所有參數(shù)都必須以抽象方法的形式存在,例如上面一樣悯嗓。接下來我們看如何使用該注解:

@myAnnotion(name = "walker",age=10)
public class Test_ann {

    public static void main(String[] args){

    }
}

之前我們說過件舵,注解本身不會(huì)起到任何作用,需要配合注解處理器才能發(fā)揮一定的作用脯厨,自己本身其實(shí)更像是一種特殊的注釋铅祸。在上例中,我們可以在()中為注解的內(nèi)部參數(shù)賦值俄认,需要注意的是个少,注解的參數(shù)不允許為null,也就是在使用注解的時(shí)候眯杏,內(nèi)部的每個(gè)參數(shù)都是必須要有數(shù)值的夜焦,要么在定義的時(shí)候給賦上默認(rèn)值(使用default關(guān)鍵字),要么在()內(nèi)顯式的賦值岂贩。允許的注解參數(shù)類型有:

  • 所有基本數(shù)據(jù)類型(int,float,boolean,byte,double,char,long,short)
  • String類型
  • Class類型
  • enum類型
  • Annotation類型
  • 以上所有類型的數(shù)組

如果我們想要表示注解中某個(gè)參數(shù)不存在茫经,該怎么辦呢?比如我們用上述自定義的注解去修飾了一個(gè)People類萎津,如果此人的age不知道卸伞,我們?cè)撊绾钨x值(參數(shù)的值不能為null)。我們往往用一些特殊值來標(biāo)記某個(gè)參數(shù)不存在的情況锉屈,例如我們可以給age賦值-1表示此人年齡不詳荤傲,在使用注解處理器讀取的時(shí)候發(fā)現(xiàn)age等于-1,我們就知道此人年齡不詳颈渊。往往字符串類型的參數(shù)用""表示參數(shù)不存在遂黍,整型類型參數(shù)使用負(fù)數(shù)表示參數(shù)不存在终佛。
    
五、使用注解處理器響應(yīng)注解
?????我們說過一個(gè)注解被定義出來之后雾家,是不能完成任何作用的铃彰,如果沒有注解處理器響應(yīng)的注解和注釋差不多。本小節(jié)我們看看如何定義一個(gè)注解處理器來對(duì)我們自定義的注解進(jìn)行響應(yīng)芯咧。還有一個(gè)前提是:我們的注解處理器實(shí)際上也是類牙捉,所以它只有在被加載到j(luò)vm中才能生效,但是如果我們的注解的生命周期范圍到不了jvm的話敬飒,注解處理器也是沒用的邪铲。
?????Java擴(kuò)充了其反射機(jī)制,使得我們可以利用反射來獲取注解信息无拗。反射中的Class霜浴,Method,Constructor蓝纲,F(xiàn)ield,Package都繼承了接口AnnotatedElement晌纫,這個(gè)接口主要有以下幾個(gè)方法:

/*判斷是否存在指定的注解*/
 boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
 
 /*獲取指定的注解*/
<T extends Annotation> T getAnnotation(Class<T> annotationClass);

/*獲取當(dāng)前元素的所有注解*/
Annotation[] getAnnotations();

/*返回直接存在于此元素上的指定的注解税迷,忽略繼承,如果沒有返回null*/
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass)

/*返回直接存在于此元素上的所有注解锹漱,忽略繼承*/
Annotation[] getDeclaredAnnotations();

下面看一個(gè)注解的簡(jiǎn)單總和實(shí)例:

@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME )  //運(yùn)行時(shí)保留

public @interface PName {

    String name() default "";
}
@Target(value={ElementType.FIELD})//修飾Filed的注解
@Retention(value = RetentionPolicy.RUNTIME )  //運(yùn)行時(shí)保留

public @interface PAge {
    int age() default 0;
}
@Target(value={ElementType.METHOD})//修飾method的注解
@Retention(value = RetentionPolicy.RUNTIME )  //運(yùn)行時(shí)保留

public @interface SayHello {

    String content() default "hello";
}
public class People {

    @PName(name = "people")
    private String name;
    @PAge(age = 20)
    private int age;

    @SayHello(content = "hello people")
    public void sayHello(){
        System.out.println("hello people");
    }
}
public static void main(String[] args) throws NoSuchMethodException {
        //獲取people類中所有注解信息
        Field[] fields = People.class.getDeclaredFields();
        for(Field f : fields){
            //遍歷每個(gè)屬性
            if(f.isAnnotationPresent(PName.class)){
                PName pn = f.getAnnotation(PName.class);
                System.out.println(pn.name());
            }else{
                PAge pa = f.getAnnotation(PAge.class);
                System.out.println(pa.age());
            }
        }
        Method md = People.class.getMethod("sayHello");
        SayHello sh = md.getAnnotation(SayHello.class);
        System.out.println(sh.content());
    }
這里寫圖片描述

上述的代碼完成了將people類中所有注解信息全部獲取打印的工作箭养。這個(gè)例子可能不能準(zhǔn)確的描述注解在我們程序中的作用(起碼注解不會(huì)用來干這個(gè)),但是在一方面演示了定義到使用注解的過程哥牍,希望對(duì)大家在項(xiàng)目中實(shí)際使用有所啟發(fā)毕泌。

最后,本篇文章結(jié)束了嗅辣,望大家多多留言交流撼泛,相互學(xué)習(xí)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末澡谭,一起剝皮案震驚了整個(gè)濱河市愿题,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛙奖,老刑警劉巖潘酗,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異雁仲,居然都是意外死亡仔夺,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門攒砖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缸兔,“玉大人日裙,你說我怎么就攤上這事≡钐澹” “怎么了阅签?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蝎抽。 經(jīng)常有香客問我政钟,道長,這世上最難降的妖魔是什么樟结? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任养交,我火速辦了婚禮,結(jié)果婚禮上瓢宦,老公的妹妹穿的比我還像新娘碎连。我一直安慰自己,他們只是感情好驮履,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布鱼辙。 她就那樣靜靜地躺著,像睡著了一般玫镐。 火紅的嫁衣襯著肌膚如雪倒戏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天恐似,我揣著相機(jī)與錄音杜跷,去河邊找鬼。 笑死矫夷,一個(gè)胖子當(dāng)著我的面吹牛葛闷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播双藕,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼淑趾,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了忧陪?” 一聲冷哼從身側(cè)響起治笨,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赤嚼,沒想到半個(gè)月后旷赖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡更卒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年等孵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蹂空。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡俯萌,死狀恐怖果录,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情咐熙,我是刑警寧澤弱恒,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站棋恼,受9級(jí)特大地震影響返弹,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爪飘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一义起、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧师崎,春花似錦默终、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至床估,卻和暖如春肴熏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背顷窒。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留源哩,地道東北人鞋吉。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像励烦,于是被迫代替她去往敵國和親谓着。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 本文章涉及代碼已放到github上annotation-study 1.Annotation為何而來 What:A...
    zlcook閱讀 29,158評(píng)論 15 116
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,079評(píng)論 25 707
  • 整體Retrofit內(nèi)容如下: 1坛掠、Retrofit解析1之前哨站——理解RESTful 2赊锚、Retrofit解析...
    隔壁老李頭閱讀 6,461評(píng)論 4 31
  • 亞里士多德曾說過舷蒲,人的靈魂有植物靈魂、動(dòng)物靈魂和理性靈魂友多。是不是萬事萬物的靈魂都是這樣分類的牲平?而隨著科學(xué)的進(jìn)一步...
    何語閱讀 1,077評(píng)論 0 0