從JDK 5開始,Java增長了對元數據(MetaData)的支持,也就是Annotation(註釋)。Annotation提供了一種爲程序元素設置元數據的方法,從某些方面來看,Annotation就想修飾符同樣,可用於修飾包、類、構造器、方法、成員變量、參數、局部變量的聲明,這些信息被存儲在Annotation的」name = value」對中java
Annotation是一個接口,程序能夠經過反射來獲取指定程序元素的Annotation對象,而後經過Annotation對象來取得註釋裏的元數據程序員
Annotation能被用來爲程序元素(類、方法、成員變量等)設置元數據,且不會影響程序代碼的執行,不管增長、刪除Annotation,代碼都始終如一地執行。若是但願讓程序中的Annotation在運行時起必定的做用,只有經過某種配套的工具對Annotation中的信息進行訪問和處理,訪問和處理Annotation的工具統稱爲APT(Annotation Processing Tool)編程
使用Annotation時要在其前面增長@符號,並把該Annotation當成一個修飾符使用,用於修飾它支持的程序元素數組
5個基本的Annotation框架
@Overridejsp
@Deprecatedide
@SuppressWarnings函數
@SafeVarargs工具
@FunctionalInterface測試
@Override 就是用來指定方法覆載,它能夠強制一個子類必須覆蓋父類的方法。@Override的做用是告訴編譯器檢查這個方法,保證父類要包含一個被該方法重寫的方法,不然就會編譯出錯。@Override主要是幫助程序員避免一些低級錯誤,如重寫info()方法,卻手誤寫成了inf(),編譯器不會報錯,你可能找半天才能找到錯誤
@Override 只能修飾方法,不能修飾其餘程序元素
@Deprecated 用於表示某個程序元素(類,方法等)已過期,當其餘程序使用已過期的類,方法時,編譯器將會給出警告
@SuppressWarnings 指示被該Annotation修飾的程序元素(以及該程序元素中的全部子元素)取消顯示指定的編譯器警告。@SuppressWarnings 會一直做用域該程序元素的全部子元素,例如,使用@SuppressWarnings修飾某個類取消顯示某個編譯器警告,同時又修飾該類裏的某個方法取消顯示另外一個編譯器警告,那麼該方法將會同時取消顯示這兩個編譯器警告
List list = new ArrayList<Integer>(); list.add(10); //添加元素時引起unchecked異常 // 下面代碼引發「未經檢查的轉換」的警告,但編譯、運行時徹底正常 List<String> temp = list; // ① // 但只要訪問temp裏的元素,就會引發運行時異常 System.out.println(temp.get(0));
「堆污染」(Heap pollution):當把一個不帶泛型的對象賦給一個帶泛型的變量時,每每就會方式這種「堆污染」
Java會在定義該方法時就發出「堆污染」警告,這樣保證開發者「更早」地注意到程序中可能存在的「漏洞」。有些時候,開發者不但願看到這個警告,則可使用以下三種方式來「抑制」這個警告
使用@SafeVarargs 修飾引起該警告的方法或構造器
使用@SuppressWarnings("unchecked")修飾
編譯時使用-Xlint:varargs選項(不多使用)
Java 8規定:若是接口中只有一個抽象方法(能夠包含多個默認方法或多個static方法),該接口就是函數式接口。該註解只可以修飾接口,不可以修飾其餘程序元素。@FunctionalInterface 只是告訴編譯器檢查這個接口,保證該接口只能包含一個抽象方法,不然就會編譯出錯
@FunctionalInterface 只能修飾接口,不能修飾其餘程序元素
JDK除了在java.lang下提供了5個基本的Annotation以外,還在java.lang.annotation包下提供了6個Meta Annotation,其中有5個元Annotation都用於修飾其餘的Annotation定義。其中@Repeatable專門用於定義Java 8新增的重複註解
@Retention 只能用於修飾Annotation定義,用於指定被修飾的Annotation能夠保留多長時間,@Retention包含一個RetentionPolicy類型的value成員變量,因此使用@Retention時候必須爲該value成員變量指定值
value成員變量的值只能是以下三個:
RetentionPolicy.CLASS:編譯器將把Annotation記錄在class文件中。當運行java程序時,JVM不能夠獲取Annotation信息。這是默認值
RetentionPolicy.RUNTIME:編譯器將把Annotation記錄在class文件中。當運行java程序時,JVM能夠獲取Annotation信息,程序也能夠經過反射獲取該Annotation信息
RetentionPolicy.SOURCE:Annotation只保留在源代碼中,編譯器直接丟棄這種Annotation
// 定義下面的Testable Annotation保留到運行時 @Retention(value = RetentionPolicy.RUNTIME) public @interface Testable{} // 定義下面的Testable Annotation將被編譯器直接丟棄 @Retention(RetentionPolicy.SOURCE) public @interface Testable{}
@Target 也只能用來修飾一個Annotation定義,它用於指定被修飾的Annotation能用於修飾哪些程序單元
其value值有以下幾個:
ElementType.ANNOTATION_TYPE:指定該策略的Annotation只能修飾Annotation
ElementType.CONSTRUCTOR:指定該策略的Annotation只能修飾構造器
ElementType.FIELD:指定該策略的Annotation只能修飾成員變量
ElementType.LOCAL_VARIABLE:指定該策略的Annotation只能修飾局部變量
ElementType.METHOD:指定該策略的Annotation只能修飾方法定義
ElementType.PACKAGE:指定該策略的Annotation只能修飾包定義
ElementType.PARAMETER:指定該策略的Annotation能夠修飾參數
ElementType.TYPE:指定該策略的Annotation能夠修飾類、接口(包括註釋類型)或枚舉定義
// 指定@ActionListenerFor Annotation只能修飾成員變量 @Target(ElementType.FIELD) public @interface ActionListenerFor{}
@Documented 用於指定被該元Annotation修飾的Annotation類將被javadoc工具提取成文檔,若是定義Annotation類時候使用了@Documented 修飾,則全部使用該Annotation修飾的程序元素的API文檔中將會包含該Annotation說明
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) // 定義Param Annotation將被javadoc工具提取 @Documented public @interface Param { long id(); String name(); String team() default "Cleveland"; } public class Person { public static void main(String[]args) { ... } // 使用@Param修飾toPerson()方法 @Param(id = 23, name = "James") public void toPerson() { ... } }
@Inherited 元Annotation指定被它修飾的Annotation將具備繼承性——若是某個類使用了@Xxx註解(定義該Annotation時使用了@Inherited修飾)修飾,則其子類將自動被@Xxx修飾
定義一個新的Annotation與定義一個接口相似,須要使用@interface關鍵字,例以下面定義了一個名爲Param的Annotation,並在Test類中使用它:
public @interface Param { }
能夠在程序的任何地方使用該Annotation,可用於修飾程序中的類、方法、變量、接口等定義。一般會把Annotation放在全部修飾符以前,另放一行
// 使用@Param修飾類定義 @Param public class Test { public static void main(String[]args) { } }
在默認狀況下,Annotation可用於修飾任何程序元素,包括類、接口、方法等。如普通方法同樣,Annotation還能夠帶成員變量,Annotation的成員變量在Annotation定義中以無形參的方法形式來聲明,其方法名和返回值定義了該成員變量的名字和類型,如:
public @interface Param { long id(); String name(); String team() default "Cleveland"; }
一旦在Annotation裏定義了成員變量,使用時就須要爲其指定值;也能夠爲成員變量指定初始值(默認值),指定成員變量的初始值可以使用default關鍵字,此時能夠不爲這些成員變量指定值
@Param(id = 2, name = "Irving") public class Animal { public static void main(String[]args) { ... } }
根據Annotation按是否包含成員變量,Annotation分爲兩類:
標記Annotation:沒有定義成員變量的Annotation類型稱爲標記。這種Annotation僅利用自身的存在與否來爲咱們提供信息,例如@Override 、@Deprecated等
元數據Annotation:包含成員變量的Annotation,由於它們能夠接受更多的元數據
使用Annotation修飾了類、方法、成員變量等成員後,這些Annotation不會本身生效,必須由開發者提供相應的工具來提取並處理Annotation信息
AnnotatedElement接口是全部程序元素(如Class、Method、Constructor等)的父接口,因此程序經過反射獲取了某個類的AnnotatedElement對象(如Class、Method、Constructor等)以後,程序就能夠調用該對象的以下幾個方法來訪問Annotation信息
<T extends Annotation> T getAnnotation(Class<T> annotationClass):返回該程序元素上存在的、指定類型的註解,若是該類型的註解不存在,則返回null
<T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass):該方法嘗試獲取直接修飾該程序元素、指定類型的Annotation。若是該類型的註解不存在,則返回null
Annotation[] getAnnotations():返回該程序元素上存在的全部註解,若沒有註解,返回長度爲0的數組
Annotation[] getDeclaredAnnotations():返回直接修飾該程序元素的全部Annotation
boolean isAnnotationPresent(Class<?extends Annotation> annotationClass) :判斷該程序元素上是否包含指定類型的註解,存在則返回true,不然返回false
<T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass):該方法的功能與getAnnotation()方法基本類似。使用該方法獲取修飾該元素、指定類型的多個Annotation
<T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass):該方法的功能與getDeclaredAnnotation()方法基本類似。使用該方法獲取直接修飾該元素、指定類型的多個Annotation
// 獲取Test類的info方法裏的全部註解,並將這些註解打印出來 Annotation[] aArray = Class.forName("Test").getMethod("info").getAnnotations(); // 遍歷全部註解 for (Annotation an : aArray) { System.out.println(an); }
若是須要獲取某個註解裏的元數據,則能夠將註解強制類型轉換成所需的主家樓下,而後經過註解對象的抽象方法來訪問這些元數據
// 獲取tt對象的info方法所包含的全部註解 Annotation[] annotation = tt.getClass.forName().getMethod("info").getAnnotations(); // 遍歷每一個註解對象 for (Annotation tag : annotation) { // 若是tag註解是MyTag1類型 if ( tag instanceof MyTag1) { System.out.println("Tag is: " + tag); // 將tag強制類型轉換偉MyTag1 // 輸出tag對象的method1和method2兩個成員變量的值 System.out.println("tag.name(): " + ((MyTag1)tag).method1()); System.out.println("tag.age(): " + ((MyTag1)tag).method2()); } // 若是tag註解是MyTag2類型 if ( tag instanceof MyTag2) { System.out.println("Tag is: " + tag); // 將tag強制類型轉換偉MyTag2 // 輸出tag對象的method1和method2兩個成員變量的值 System.out.println("tag.name(): " + ((MyTag2)tag).method1()); System.out.println("tag.age(): " + ((MyTag2)tag).method2()); } }
Annotation
Testable沒有任何成員變量,僅是一個標記Annotation,做用是標記哪些方法是可測試的。程序經過判斷該Annotation存在與否來決定是否運行指定方法
import java.lang.annotation.*; // 使用JDK的元數據Annotation:Retention @Retention(RetentionPolicy.RUNTIME) // 使用JDK的元數據Annotation:Target @Target(ElementType.METHOD) // 定義一個標記註解,不包含任何成員變量,即不可傳入元數據 public @interface Testable { }
@Testable 用於標記哪些方法是可測試的,該Annotation能夠做爲JUnit測試框架的補充。在JUnit框架中,測試用例的測試方法必須以test開頭。若是使用@Testable 註解,則可把任何方法標記爲可測試的
public class MyTest { // 使用@Testable註解指定該方法是可測試的 @Testable public static void m1() { } public static void m2() { } // 使用@Testable註解指定該方法是可測試的 @Testable public static void m3() { throw new IllegalArgumentException("參數出錯了!"); } public static void m4() { } // 使用@Testable註解指定該方法是可測試的 @Testable public static void m5() { } public static void m6() { } // 使用@Testable註解指定該方法是可測試的 @Testable public static void m7() { throw new RuntimeException("程序業務出現異常!"); } public static void m8() { } }
註解處理工具分析目標類,若是目標類中的方法使用了@Testable 註解修飾,則經過反射來運行該測試方法
import java.lang.reflect.*; public class ProcessorTest { public static void process(String clazz) throws ClassNotFoundException { int passed = 0; int failed = 0; // 遍歷clazz對應的類裏的全部方法 for (Method m : Class.forName(clazz).getMethods()) { // 若是該方法使用了@Testable修飾 if (m.isAnnotationPresent(Testable.class)) { try { // 調用m方法 m.invoke(null); // 測試成功,passed計數器加1 passed++; } catch (Exception ex) { System.out.println("方法" + m + "運行失敗,異常:" + ex.getCause()); // 測試出現異常,failed計數器加1 failed++; } } } // 統計測試結果 System.out.println("共運行了:" + (passed + failed) + "個方法,其中:\n" + "失敗了:" + failed + "個,\n" + "成功了:" + passed + "個!"); } }
public class RunTests { public static void main(String[] args) throws Exception { // 處理MyTest類 ProcessorTest.process("MyTest"); } }
經過使用Annotation來簡化事件編程,在傳統的事件編程中老是須要經過addActionListener()方法來爲事件源綁定事件監聽器,下面的示例經過@ActionListenerFor來爲程序中的按鈕綁定事件監聽器
import java.lang.annotation.*; import java.awt.event.*; @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ActionListenerFor { // 定義一個成員變量,用於設置元數據 // 該listener成員變量用於保存監聽器實現類 Class<? extends ActionListener> listener(); }
使用@ActionListenerFor 註解來爲兩個按鈕綁定事件監聽器
import java.awt.event.*; import javax.swing.*; public class AnnotationTest { private JFrame mainWin = new JFrame("使用註解綁定事件監聽器"); // 使用Annotation爲ok按鈕綁定事件監聽器 @ActionListenerFor(listener=OkListener.class) private JButton ok = new JButton("肯定"); // 使用Annotation爲cancel按鈕綁定事件監聽器 @ActionListenerFor(listener=CancelListener.class) private JButton cancel = new JButton("取消"); public void init() { // 初始化界面的方法 JPanel jp = new JPanel(); jp.add(ok); jp.add(cancel); mainWin.add(jp); ActionListenerInstaller.processAnnotations(this); // ① mainWin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mainWin.pack(); mainWin.setVisible(true); } public static void main(String[] args) { new AnnotationTest().init(); } } // 定義ok按鈕的事件監聽器實現類 class OkListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null , "單擊了確認按鈕"); } } // 定義cancel按鈕的事件監聽器實現類 class CancelListener implements ActionListener { public void actionPerformed(ActionEvent evt) { JOptionPane.showMessageDialog(null , "單擊了取消按鈕"); } }
定義了兩個JButton按鈕,並使用@ActionListenerFor 註解爲這兩個按鈕綁定了事件監聽器,使用@ActionListenerFor 註解時傳入了listener元數據,該數據用於設定每一個按鈕的監聽器實現類。程序①處代碼使用ActionListenerInstaller類來處理本程序中的註解,該處理器分析目標對象中的全部成員變量,若是該成員變量籤使用了@ActionListenerFor修飾,則取出該Annotation中的listener元數據,並根據該數據來綁定事件監聽器
import java.lang.reflect.*; import java.awt.event.*; import javax.swing.*; public class ActionListenerInstaller { // 處理Annotation的方法,其中obj是包含Annotation的對象 public static void processAnnotations(Object obj) { try { // 獲取obj對象的類 Class cl = obj.getClass(); // 獲取指定obj對象的全部成員變量,並遍歷每一個成員變量 for (Field f : cl.getDeclaredFields()) { // 將該成員變量設置成可自由訪問。 f.setAccessible(true); // 獲取該成員變量上ActionListenerFor類型的Annotation ActionListenerFor a = f.getAnnotation(ActionListenerFor.class); // 獲取成員變量f的值 Object fObj = f.get(obj); // 若是f是AbstractButton的實例,且a不爲null if (a != null && fObj != null && fObj instanceof AbstractButton) { // 獲取a註解裏的listner元數據(它是一個監聽器類) Class<? extends ActionListener> listenerClazz = a.listener(); // 使用反射來建立listner類的對象 ActionListener al = listenerClazz.newInstance(); AbstractButton ab = (AbstractButton)fObj; // 爲ab按鈕添加事件監聽器 ab.addActionListener(al); } } } catch (Exception e) { e.printStackTrace(); } } }
根據@ActionListenerFor註解的元數據取得了監聽器實現類,而後經過反射來建立監聽器對象,接下來將監聽器對象綁定到指定的按鈕(按鈕由被@ActionListenerFor修飾的Field表示)
Java8容許使用多個相同類型的Annotation來修飾同一個類
@Result (name = "failure", location = "failed.jsp") @Result (name = "success", location = "succ.jsp") public Acton FooAction{...}
若是定義了@FkTag(無@Repeatable版)註解,該註解包括兩個成員變量。但該註解默認不能做爲重複註解使用,若是使用兩個以上的以下註解修飾同一個類,編譯器會報錯
開發重複註解須要使用@Repeatable 修飾。使用@Repeatable修飾該註解,使用@Repeatable時必須爲value成員變量指定值,該成員變量的值應該是一個「容器」註解——該容器註解能夠包含多個@FkTag
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Repeatable(FkTags.class) public @interface FkTag { // 爲該註解定義2個成員變量 String name() default "NBA球員"; int number(); }
「容器」註解可包含多個@FkTag,所以須要定義以下的「容器」註解
import java.lang.annotation.*; @Retention(RetentionPolicy.RUNTIME) // ① @Target(ElementType.TYPE) public @interface FkTags { // 定義value成員變量,該成員變量可接受多個@FkTag註解 FkTag[] value(); // ② }
代碼①指定了@FkTags 註解信息可保留到運行時,這是必需的,由於@FkTag 註解信息須要保留到運行時,若是@FkTags 註解只能保留到源代碼級別或類文件,將會致使@FkTags 的保留期小於@FkTag 的保留期,若是程序將多個@FkTag註解放入@FkTags中,若JVM丟棄了@FkTags註解,天然也就丟棄了@FkTag的信息
代碼②定義了一個FkTag[]類型的value成員變量,這意味着@FkTags 註解的value成員變量可接受多個@FkTags 註解可做爲@FkTag 的容器
「容器」註解的保留期必須必它所包含的註解的保留期更長,不然編譯器會報錯
傳統代碼使用該註解
@FkTags({@FkTag(number = 23), @FkTag(name = "Westbrooks", number = 0)})
因爲@FkTags是重複註解,所以可直接使用兩個@FkTag註解,系統依然將兩個@FkTag註解做爲@FkTags的values成員變量的數組元素
@FkTag(number = 23) @FkTag(name = "Westbrooks", number = 0)
重複註解是一種簡化寫法,這種簡化寫法是一種假象:多個重複註解會被做爲「容器」註解的value成員變量的數組元素
@FkTag(number = 23) @FkTag(name = "Westbrooks", number = 0) public class FkTagTest { public static void main(String[] args) { Class<FkTagTest> clazz = FkTagTest.class; /* 使用Java 8新增的getDeclaredAnnotationsByType()方法獲取 修飾FkTagTest類的多個@FkTag註解 */ FkTag[] tags = clazz.getDeclaredAnnotationsByType(FkTag.class); // 遍歷修飾FkTagTest類的多個@FkTag註解 for(FkTag tag : tags) { System.out.println(tag.name() + "-->" + tag.age()); } } }
運行結果:
NBA球員-->23 Westbrooks-->0 @FkTags(value=[@FkTag(name=NBA球員, age=23), @FkTag(name=Westbrooks, age=0)])
Java8爲ElementType枚舉增長了TYPE_PARAMETER、TYPE_USE兩個枚舉值,容許定義枚舉時使用@Target(ElementType.TYPE_USE)修飾,這種註解稱爲Type Annotation(類型註解),Type Annotation可用在任何用到類型的地方
容許在以下位置使用Type Annotation
建立對象(用new關鍵字建立)
類型轉換
使用implements實現接口
使用throws聲明拋出異常
import java.util.*; import java.io.*; import javax.swing.*; import java.lang.annotation.*; // 定義一個簡單的Type Annotation,不帶任何成員變量 @Target(ElementType.TYPE_USE) @interface NotNull{} // 定義類時使用Type Annotation @NotNull public class TypeAnnotationTest implements @NotNull /* implements時使用Type Annotation */ Serializable { // 方法形參中使用Type Annotation public static void main(@NotNull String[] args) // throws時使用Type Annotation throws @NotNull FileNotFoundException { Object obj = "fkjava.org"; // 強制類型轉換時使用Type Annotation String str = (@NotNull String)obj; // 建立對象時使用Type Annotation Object win = new @NotNull JFrame("俄克拉荷馬雷霆"); } // 泛型中使用Type Annotation public void foo(List<@NotNull String> info){} }