掌握 Java 注解,一夜陸地神仙
一、注解簡介
Java注解用于為Java代碼提供元數(shù)據(jù)。
元數(shù)據(jù)是指用來描述數(shù)據(jù)的數(shù)據(jù),通俗一點,就是描述代碼間關(guān)系,或者代碼與其它資源(例如數(shù)據(jù)庫表)之間內(nèi)在聯(lián)系的數(shù)據(jù)。在一些技術(shù)框架中,如Struts、hibernate就不知不覺用到了元數(shù)據(jù)。對于Struts來說,元數(shù)據(jù)指的是struts-config.xml;對hibernate來說就是hbm文件。以上闡述的幾種元數(shù)據(jù)都是基于xml文件的或者其他形式的單獨(dú)配置文件。這樣表示有些不便之處。1、與被描述的文件分離,不利于一致性的維護(hù);2、所有這樣的文件都是ASCII文件,沒有顯式的類型支持?;谠獢?shù)據(jù)的廣泛使用,JDK5.0引入了Annotation的概念來描述元數(shù)據(jù)。在Java中,元數(shù)據(jù)以標(biāo)簽的形式存在于Java代碼中,元數(shù)據(jù)標(biāo)簽的存在并不影響程序代碼的編譯和執(zhí)行。簡而言之,言而總之,注解就是標(biāo)簽的意思。
二、如何創(chuàng)建注解
JDK5.0出來后,Java語言中就有了四種類型,即類class、枚舉enum、接口interface、注解@interface,它們處于同一級別,Java就是通過注解來表示元數(shù)據(jù)的。
- package com.guor.ClientNew;
- public @interface MyAnnotation {
- // 定義公共的final靜態(tài)屬性
- int age = 25;
- // 定義公共的抽象方法
- String name();
- }
Java注解本質(zhì)上就是接口,是繼承了Annotation接口的接口。
三、元注解
元注解是可以注解到注解上的注解,或者說元注解是一種基本注解,它能夠應(yīng)用到其它的注解上面。
元標(biāo)簽有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。
1、@Retention
Retention,中文釋義保留期的意思
當(dāng)@Retention應(yīng)用到注解上的時候,它解釋說明了這個注解的生命周期。
- RetentionPolicy.SOURCE 注解只在源碼階段保留,在編譯器進(jìn)行編譯時它將被丟棄忽視。
- RetentionPolicy.CLASS 注解只被保留到編譯進(jìn)行的時候,它并不會被加載到JVM中。
- RetentionPolicy.RUNTIME 注解可以保留到程序運(yùn)行的時候,它會被加載到JVM中。
2、@Documented
顧名思義,這個元注解肯定和文檔有關(guān)。它的作用是能夠?qū)⒆⒔庵械脑匕絁avadoc中去。
3、@Target
標(biāo)明注解運(yùn)用的地方。
- ElementType.ANNOTATION_TYPE 可以給一個注解進(jìn)行注解
- ElementType.CONSTRUCTOR 可以給構(gòu)造方法進(jìn)行注解
- ElementType.FIELD 可以給屬性進(jìn)行注解
- ElementType.LOCAL_VARIABLE 可以給局部變量進(jìn)行注解
- ElementType.METHOD 可以給方法進(jìn)行注解
- ElementType.PACKAGE 可以給一個包進(jìn)行注解
- ElementType.PARAMETER 可以給一個方法內(nèi)的參數(shù)進(jìn)行注解
- ElementType.TYPE 可以給一個類型進(jìn)行注解,比如類、接口、枚舉
4、@Inherited
lnherited是繼承的意思。
如果一個超類被@Inherited注解過的注解進(jìn)行注解的話,那么如果它的子類沒有被任何注解應(yīng)用的話,那么這個子類就繼承了超類的注解。
代碼實例
5、@Repeatable
Repeatable 自然是可重復(fù)的意思。@Repeatable 是 Java 1.8 才加進(jìn)來的,所以算是一個新的特性。
什么樣的注解會多次應(yīng)用呢?通常是注解的值可以同時取多個。
在生活中一個人往往是具有多種身份,如果我把每種身份當(dāng)成一種注解該如何使用???
先聲明一個Persons類用來包含所有的身份
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface Persons {
- Person[] value();
- }
這里@Target是聲明Persons注解的作用范圍,參數(shù)ElementType.Type代表可以給一個類型進(jìn)行注解,比如類,接口,枚舉。
@Retention是注解的有效時間,RetentionPolicy.RUNTIME是指程序運(yùn)行的時候。
Person注解:
- @Repeatable(Persons.class)
- public @interface Person{
- String role() default "";
- }
@Repeatable括號內(nèi)的就相當(dāng)于用來保存該注解內(nèi)容的容器。
聲明一個Man類,給該類加上一些身份。
- @Person(role="CEO")
- @Person(role="husband")
- @Person(role="father")
- @Person(role="son")
- public class Man {
- String name="";
- }
在主方法中訪問該注解:
- public static void main(String[] args) {
- Annotation[] annotations = Man.class.getAnnotations();
- System.out.println(annotations.length);
- Persons p1=(Persons) annotations[0];
- for(Person t:p1.value()){
- System.out.println(t.role());
- }
- }
下面的代碼結(jié)果輸出相同,但是可以先判斷是否是相應(yīng)的注解,比較嚴(yán)謹(jǐn)。
- if(Man.class.isAnnotationPresent(Persons.class)) {
- Persons p2=Man.class.getAnnotation(Persons.class);
- for(Person t:p2.value()){
- System.out.println(t.role());
- }
- }
運(yùn)行結(jié)果:
四、注解的屬性
注解的屬性也叫做成員變量,注解只有成員變量,沒有方法。注解的成員變量在注解的定義中以“無參的方法”形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation {
- int id();
- String msg();
- }
上面代碼中定義了TestAnnotation這個注解中擁有id和msg兩個屬性。在使用的時候,我們應(yīng)該給他們進(jìn)行賦值。
賦值的方式是在注解的括號內(nèi)以value=“”形式,多個屬性之前用,隔開。
- @TestAnnotation(id=3,msg="hello annotation")
- public class Test {
- }
需要注意的是,在注解中定義屬性時它的類型必須是 8 種基本數(shù)據(jù)類型外加 類、接口、注解及它們的數(shù)組。
注解中屬性可以有默認(rèn)值,默認(rèn)值需要用 default 關(guān)鍵值指定。比如:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface TestAnnotation {
- public int id() default -1;
- public String msg() default "哪吒";
- }
TestAnnotation 中 id 屬性默認(rèn)值為 -1,msg 屬性默認(rèn)值為 哪吒。
它可以這樣應(yīng)用。
- @TestAnnotation()
- public class Test {}
因為有默認(rèn)值,所以無需要再在 @TestAnnotation 后面的括號里面進(jìn)行賦值了,這一步可以省略。
另外,還有一種情況。如果一個注解內(nèi)僅僅只有一個名字為 value 的屬性時,應(yīng)用這個注解時可以直接將屬性值填寫到括號內(nèi)。
- public @interface Check {
- String value();
- }
上面代碼中,Check 這個注解只有 value 這個屬性。所以可以這樣應(yīng)用。
- @Check("hi")
- int a;
這和下面的效果是一樣的
- @Check(value="hi")
- int a;
最后,還需要注意的一種情況是一個注解沒有任何屬性。比如:
- public @interface Perform {}
那么在應(yīng)用這個注解的時候,括號都可以省略。
- @Perform
- public void testMethod(){}
五、Java自帶的注解
學(xué)習(xí)了上面相關(guān)的知識,我們已經(jīng)可以自己定義一個注解了。其實 Java 語言本身已經(jīng)提供了幾個現(xiàn)成的注解。
1、@Override
這個大家應(yīng)該很熟悉了,提示子類要復(fù)寫父類中被 @Override 修飾的方法
2、@Deprecated
加上這個注解之后,表示此方法或類不再建議使用,調(diào)用時會出現(xiàn)刪除線,但不代表不能用,只是說,不推薦使用,因為有更好的方法可以調(diào)用。
那么直接刪掉不就完了?
因為在一個項目中,工程比較大,代碼比較多,而在后續(xù)的開發(fā)過程中,可能之前的某個方法實現(xiàn)的并不是很合理,這個時候要重新寫一個方法,而之前的方法還不能隨便刪,因為別的地方可能在調(diào)用它,所以加上這個注解,就OK啦!
3、@SuppressWarning
阻止警告的意思。
該批注的作用是給編譯器一條指令,告訴它對被批注的代碼元素內(nèi)部的某些警告保持靜默。
4、@SafeVarargs
參數(shù)安全類型注解。
它的目的是提醒開發(fā)者不要用參數(shù)做一些不安全的操作,它的存在會阻止編譯器產(chǎn)生unchecked這樣的警告。
在聲明具有模糊類型(比如:泛型)的可變參數(shù)的構(gòu)造函數(shù)或方法時,Java編譯器會報unchecked警告。鑒于這種情況,如果程序猿斷定聲明的構(gòu)造函數(shù)和方法的主體no problem,可使用@SafeVarargs進(jìn)行標(biāo)記,這樣Java編譯器就不會報unchecked警告了!
5、@FunctionalInterface
Java 8為函數(shù)式接口引入了一個新注解@FunctionalInterface,主要用于編譯級錯誤檢查,加上該注解,當(dāng)你寫的接口不符合函數(shù)式接口定義的時候,編譯器會報錯。
它們主要用在Lambda表達(dá)式和方法引用(實際上也可認(rèn)為是Lambda表達(dá)式)上。
如定義了一個函數(shù)式接口如下:
- @FunctionalInterface
- interface GreetingService
- {
- void sayMessage(String message);
- }
那么就可以使用Lambda表達(dá)式來表示該接口的一個實現(xiàn)(注:JAVA 8 之前一般是用匿名類實現(xiàn)的):
- GreetingService greetService1
- = message -> System.out.println("Hello " + message);
六、注解的使用場景
1、注解的官方釋義
注解是一系列元數(shù)據(jù),它提供數(shù)據(jù)用來解釋程序代碼,但是注解并非是所解釋的代碼本身的一部分。注解對于代碼的運(yùn)行效果沒有直接影響。
2、注解的用處
① 提供信息給編譯器:編譯器可以利用注解來探測錯誤或警告信息
② 編譯階段時的處理:軟件工具可以利用注解信息來生成代碼、HTML文檔或其它響應(yīng)處理。
③ 運(yùn)行時的處理:某些注解可以在程序運(yùn)行時接受代碼的提取。
值得注意的是,注解不是代碼本身的一部分。
3、注解的用法舉例
- public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
- }
@Test 標(biāo)記了要進(jìn)行測試的方法 addition_isCorrect().
還有例如ssm框架等運(yùn)用了大量的注解。
七、注解的本質(zhì)
注解本質(zhì)是一個繼承了Annotation的特殊接口,其具體實現(xiàn)類是Java運(yùn)行時生成的動態(tài)代理類。通過代理對象調(diào)用其自定義注解的方法,最終調(diào)用的是AnnotationInvocationHandler的invoke方法,該方法會從memberValues這個map中索引出對應(yīng)的值,而memberValues的來源是Java常量池。
八、總結(jié)
1、注解就是標(biāo)簽,注解為了解釋代碼
2、注解的基本語法@interface
3、注解的元注解
4、注解的屬性
5、注解主要給編譯器及工具類型的軟件用的
6、注解的提取要借助于Java的反射技術(shù),反射比較慢,所以注解使用時也需要謹(jǐn)慎計較時間成本
本文轉(zhuǎn)載自微信公眾號「哪吒學(xué)Java」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系哪吒學(xué)Java公眾號。