前言:Annotation中文釋義註解之意。java
Java註解用於爲Java代碼提供元數據。數據庫
元數據是指用來描述數據的數據,通俗一點,就是描述代碼間關係,或者代碼與其它資源(例如數據庫表)之間內在聯繫的數據。在一些技術框架中,如Struts、hibernate就不知不覺用到了元數據。對於Struts來講,元數據指的是struts-config.xml;對hibernate來講就是hbm文件。以上闡述的幾種元數據都是基於xml文件的或者其餘形式的單獨配置文件。這樣表示有些不便之處。一、與被描述的文件分離,不利於一致性的維護;二、全部這樣的文件都是ASCII文件,沒有顯式的類型支持。基於元數據的普遍使用,JDK5.0引入了Annotation的概念來描述元數據。在Java中,元數據以標籤的形式存在於Java代碼中,元數據標籤的存在並不影響程序代碼的編譯和執行。簡而言之,言而總之,註解就是標籤的意思。數組
1、如何建立註解?安全
JDK5.0出來後,Java語言中就有了四種類型,即類class、枚舉enum、接口interface、註解@interface,它們處於同一級別,Java就是經過註解來表示元數據的。框架
package OSChina.ClientNew; public @interface MyAnnotation { // 定義公共的final靜態屬性 int age = 25; // 定義公共的抽象方法 String name(); }
Java註解本質上就是接口,是繼承了Annotation接口的接口。ide
2、元註解函數
元註解是能夠註解到註解上的註解,或者說元註解是一種基本註解,它可以應用到其它的註解上面。工具
元標籤有 @Retention、@Documented、@Target、@Inherited、@Repeatable 5 種。oop
一、@Retention學習
Retention,中文釋義保留期的意思
當@Retention應用到註解上的時候,它解釋說明了這個註解的生命週期。
二、@Documented
顧名思義,這個元註解確定和文檔有關。它的做用是可以將註解中的元素包含到Javadoc中去。
三、@Target
標明註解運用的地方。
四、@Inherited
lnherited是繼承的意思。
若是一個超類被@Inherited註解過的註解進行註解的話,那麼若是它的子類沒有被任何註解應用的話,那麼這個子類就繼承了超類的註解。
代碼實例
package OSChina.ClientNew; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME)//註解能夠保留到程序運行時,加載到JVM中 @Target(ElementType.TYPE)//給一個類型進行註解,好比類、接口、枚舉 @Inherited //子類繼承父類時,註解會起做用 public @interface Desc { enum Color { White, Grayish, Yellow } // 默認顏色是白色的 Color c() default Color.White; }
五、@Repeatable
Repeatable 天然是可重複的意思。@Repeatable 是 Java 1.8 才加進來的,因此算是一個新的特性。
什麼樣的註解會屢次應用呢?一般是註解的值能夠同時取多個。
在生活中一我的每每是具備多種身份,若是我把每種身份當成一種註解該如何使用???
先聲明一個Persons類用來包含全部的身份
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface Persons { Person[] value(); }
這裏@Target是聲明Persons註解的做用範圍,參數ElementType.Type表明能夠給一個類型進行註解,好比類,接口,枚舉。
@Retention是註解的有效時間,RetentionPolicy.RUNTIME是指程序運行的時候。
Person註解:
@Repeatable(Persons.class) public @interface Person{ String role() default ""; }
@Repeatable括號內的就至關於用來保存該註解內容的容器。
聲明一個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()); } }
下面的代碼結果輸出相同,可是能夠先判斷是不是相應的註解,比較嚴謹。
if(Man.class.isAnnotationPresent(Persons.class)) { Persons p2=Man.class.getAnnotation(Persons.class); for(Person t:p2.value()){ System.out.println(t.role()); } }
運行結果:
3、註解的屬性
註解的屬性也叫作成員變量,註解只有成員變量,沒有方法。註解的成員變量在註解的定義中以「無參的方法」形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { int id(); String msg(); }
上面代碼中定義了TestAnnotation這個註解中擁有id和msg兩個屬性。在使用的時候,咱們應該給他們進行賦值。
賦值的方式是在註解的括號內以value=「」形式,多個屬性以前用,隔開。
@TestAnnotation(id=3,msg="hello annotation") public class Test { }
須要注意的是,在註解中定義屬性時它的類型必須是 8 種基本數據類型外加 類、接口、註解及它們的數組。
註解中屬性能夠有默認值,默認值須要用 default 關鍵值指定。好比:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { public int id() default -1; public String msg() default "江疏影"; }
TestAnnotation 中 id 屬性默認值爲 -1,msg 屬性默認值爲 江疏影。
它能夠這樣應用。
@TestAnnotation() public class Test {}
由於有默認值,因此無須要再在 @TestAnnotation 後面的括號裏面進行賦值了,這一步能夠省略。
另外,還有一種狀況。若是一個註解內僅僅只有一個名字爲 value 的屬性時,應用這個註解時能夠直接接屬性值填寫到括號內。
public @interface Check { String value(); }
上面代碼中,Check 這個註解只有 value 這個屬性。因此能夠這樣應用。
@Check("hi") int a;
這和下面的效果是同樣的
@Check(value="hi") int a;
最後,還須要注意的一種狀況是一個註解沒有任何屬性。好比
public @interface Perform {}
那麼在應用這個註解的時候,括號均可以省略。
@Perform public void testMethod(){}
4、Java預置的註解
學習了上面相關的知識,咱們已經能夠本身定義一個註解了。其實 Java 語言自己已經提供了幾個現成的註解。
一、@Override
這個你們應該很熟悉了,提示子類要複寫父類中被 @Override 修飾的方法
二、@Deprecated
加上這個註解以後,表示此方法或類再也不建議使用,調用時會出現刪除線,但不表明不能用,只是說,不推薦使用,由於有更好的方法能夠調用。
那麼直接刪掉不就完了?
由於在一個項目中,工程比較大,代碼比較多,而在後續的開發過程當中,可能以前的某個方法實現的並非很合理,這個時候要從新寫一個方法,而以前的方法還不能隨便刪,由於別的地方可能在調用它,因此加上這個註解,就OK啦!
package OSChina.ClientNew; import java.util.ArrayList; import java.util.List; public class Hero { @Deprecated public void say(){ System.out.println("nothing has to say!"); } public void speak(){ System.out.println("i have a dream!"); } public void addItems(String item){ List items = new ArrayList(); items.add(item); System.out.println("i am "+items); } }
三、@SuppressWarnings
阻止警告的意思。
該批註的做用是給編譯器一條指令,告訴它對被批註的代碼元素內部的某些警告保持靜默。
注:這個註解有不少參數,這裏就很少作贅述了,若有須要,請自行百度!
四、@SafeVarargs
參數安全類型註解。
它的目的是提醒開發者不要用參數作一些不安全的操做,它的存在會阻止編譯器產生unchecked這樣的警告。
在聲明具備模糊類型(好比:泛型)的可變參數的構造函數或方法時,Java編譯器會報unchecked警告。鑑於這種狀況,若是程序猿判定聲明的構造函數和方法的主體no problem,可以使用@SafeVarargs進行標記,這樣Java編譯器就不會報unchecked警告了!
先看看@SafeVarargs在Java SE中的聲明:
package java.lang; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) public @interface SafeVarargs {}
由Java源代碼聲明咱們瞭解到:@SafeVarargs註解,只能用於標記構造函數和方法,因爲保留策略聲明爲RUNTIME,因此此註解能夠在運行時生效。
@SafeVarargs註解,只能用於static或final的方法。
代碼實例:
泛型參數的方法,不加註解的狀況:
package OSChina.ClientNew; public class SafeVarargsAnnotation<S> { private S[] args; public SafeVarargsAnnotation(S... args){ this.args = args; } public void loopPrintArgs(S... args){ for (S arg : args){ System.out.println(arg); } } public final void printSelfArgs(S... args){ for (S arg : this.args) { System.out.println(arg); } } public static <T> void loopPrintInfo(T... infos){ for(T info:infos){ System.out.println(info); } } public static void main(String[] args) { SafeVarargsAnnotation.loopPrintInfo("A","B","C"); } }
註解的正確使用方式:
package OSChina.ClientNew; public class SafeVarargsAnnotation<S> { private S[] args; //構造函數可使用@SafeVarargs標記 @SafeVarargs public SafeVarargsAnnotation(S... args){ this.args = args; } //此處不能使用@SafeVarargs,由於此方法未聲明爲static或final方法, // 若是要抑制unchecked警告,可使用@SuppressWarnings註解 @SuppressWarnings("unchecked") public void loopPrintArgs(S... args){ for (S arg : args){ System.out.println(arg); } } //final方法可使用@SafeVarargs標記 @SafeVarargs public final void printSelfArgs(S... args){ for (S arg : this.args) { System.out.println(arg); } } //static方法可使用@SafeVarargs標記 @SafeVarargs public static <T> void loopPrintInfo(T... infos){ for(T info:infos){ System.out.println(info); } } public static void main(String[] args) { SafeVarargsAnnotation.loopPrintInfo("A","B","C"); } }
五、@FunctionalInterface
Java 8爲函數式接口引入了一個新註解@FunctionalInterface,主要用於編譯級錯誤檢查,加上該註解,當你寫的接口不符合函數式接口定義的時候,編譯器會報錯。
它們主要用在Lambda表達式和方法引用(實際上也可認爲是Lambda表達式)上。
如定義了一個函數式接口以下:
@FunctionalInterface interface GreetingService { void sayMessage(String message); }
那麼就可使用Lambda表達式來表示該接口的一個實現(注:JAVA 8 以前通常是用匿名類實現的):
GreetingService greetService1 = message -> System.out.println("Hello " + message);
5、註解與反射
一、註解經過反射獲取。首先能夠經過 Class 對象的 isAnnotationPresent() 方法判斷它是否應用了某個註解。
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {}
二、或者是 getAnnotations() 方法。
public Annotation[] getAnnotations() {}
前一種方法返回指定類型的註解,後一種方法返回註解到這個元素上的全部註解。
三、代碼實例:
① 沒加註解的時候:
package OSChina.ClinetNew1.Annotation; public class Test { public static void main(String[] args) { boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if(hasAnnotation){ TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println("id:"+testAnnotation.id()); System.out.println("msg:"+testAnnotation.msg()); } } }
屁都沒有!
② 加上註解
package OSChina.ClinetNew1.Annotation; @TestAnnotation public class Test { public static void main(String[] args) { boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if(hasAnnotation){ TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); System.out.println("id:"+testAnnotation.id()); System.out.println("msg:"+testAnnotation.msg()); } } }
這個正是 TestAnnotation 中 id 和 msg 的默認值。
上面的例子只是檢閱出了註解在類上的註解,其實屬性、方法上的註解也是同樣的。一樣仍是要假手與反射。
③ 屬性和方法上的註解:
package OSChina.ClinetNew1.Annotation; public @interface Check { String value(); }
package OSChina.ClinetNew1.Annotation; public @interface Perform { }
package OSChina.ClinetNew1.Annotation; import OSChina.ClientNew.Hero; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; @TestAnnotation(msg="hello") public class Test { @Check(value="hi") int a; @Perform public void testMethod(){} @SuppressWarnings("deprecation") public void test1(){ Hero hero = new Hero(); hero.say(); hero.speak(); } public static void main(String[] args) { boolean hasAnnotation = Test.class.isAnnotationPresent(TestAnnotation.class); if ( hasAnnotation ) { TestAnnotation testAnnotation = Test.class.getAnnotation(TestAnnotation.class); //獲取類的註解 System.out.println("id:"+testAnnotation.id()); System.out.println("msg:"+testAnnotation.msg()); } try { Field a = Test.class.getDeclaredField("a"); a.setAccessible(true); //獲取一個成員變量上的註解 Check check = a.getAnnotation(Check.class); if ( check != null ) { System.out.println("check value:"+check.value()); } Method testMethod = Test.class.getDeclaredMethod("testMethod"); if ( testMethod != null ) { // 獲取方法中的註解 Annotation[] ans = testMethod.getAnnotations(); for( int i = 0;i < ans.length;i++) { System.out.println("method testMethod annotation:"+ans[i].annotationType().getSimpleName()); } } } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } catch (SecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); System.out.println(e.getMessage()); } } }
須要注意的是,若是一個註解要在運行時被成功提取,那麼 @Retention(RetentionPolicy.RUNTIME) 是必須的。
6、註解的使用場景
一、註解的官方釋義:
註解是一系列元數據,它提供數據用來解釋程序代碼,可是註解並不是是所解釋的代碼自己的一部分。註解對於代碼的運行效果沒有直接影響。
二、註解有許多用處:
① 提供信息給編譯器:編譯器能夠利用註解來探測錯誤或警告信息
② 編譯階段時的處理:軟件工具能夠利用註解信息來生成代碼、HTML文檔或其它響應處理。
③ 運行時的處理:某些註解能夠在程序運行時接受代碼的提取。
值得注意的是,註解不是代碼自己的一部分。
三、註解運用的地方太多了,好比JUnit測試框架,典型的使用方法:
public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } }
@Test 標記了要進行測試的方法 addition_isCorrect().
還有例如ssm框架等運用了大量的註解。
7、註解的應用實例
package OSChina.ClientNew; import java.lang.annotation.ElementType; import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Inherited public @interface Desc { enum Color { White, Grayish, Yellow } // 默認顏色是白色的 Color c() default Color.White; }
該註解Desc前增長了三個註解:Retention表示的是該註解的保留級別,Target表示的是註解能夠標註在什麼地方,@Inherited表示該註解會被自動繼承。
package OSChina.ClinetNew1; @Desc(c = Desc.Color.White) abstract class Bird { public abstract Desc.Color getColor(); }
package OSChina.ClinetNew1; public enum BirdNest { Sparrow; // 鳥類繁殖 public Bird reproduce() { Desc bd = Sparrow.class.getAnnotation(Desc.class); return bd == null ? new Sparrow() : new Sparrow(bd.c()); } }
package OSChina.ClinetNew1; public class Sparrow extends Bird { private Desc.Color color; // 默認是淺灰色 public Sparrow() { color = Desc.Color.Grayish; } // 構造函數定義鳥的顏色 public Sparrow(Desc.Color _color) { color = _color; } @Override public Desc.Color getColor() { return color; } }
上面程序聲明瞭一個Bird抽象類,而且標註了Desc註解,描述鳥類的顏色是白色,而後編寫一個麻雀Sparrow類,它有兩個構造函數,一個是默認的構造函數,也就是咱們常常看到的麻雀是淺灰色的,另一個構造函數是自定義麻雀的顏色,以後又定義了一個鳥巢(工廠方法模式),它是專門負責鳥類繁殖的,它的生產方法reproduce會根據實現類註解信息生成不一樣顏色的麻雀。咱們編寫一個客戶端調用,代碼以下:
public static void main(String[] args) { Bird bird = BirdNest.Sparrow.reproduce(); Desc.Color color = bird.getColor(); System.out.println("Bird's color is :" + color); }
會打印出什麼呢?由於採用了工廠方法模式,它主要的問題是bird比那裏到底採用了哪一個構造函數來生成,若是單獨看子類Sparrow,它沒有任何註釋,那工廠方法中bd變量應該就是null了,應該調用無參構造!
輸出爲何會是白色呢?這是咱們添加到父類的顏色,why?這是由於咱們在註解上加了@Inherited註解,它表示的意思是咱們只要把註解@Desc加到父類Bird上,它的全部子類都會從父類繼承@Desc註解。
8、總結
一、註解就是標籤,註解爲了解釋代碼
二、註解的基本語法@interface
三、註解的元註解
四、註解的屬性
五、註解主要給編譯器及工具類型的軟件用的
六、註解的提取要藉助於Java的反射技術,反射比較慢,因此註解使用時也須要謹慎計較時間成本