摘要:從開始,增加了對元數據的支持,也就是注釋。該注解只能夠修飾接口,不能夠修飾其他程序元素。程序處代碼使用類來處理本程序中的注解,該處理器分析目標對象中的所有成員變量,如果該成員變量簽使用了修飾,則取出
從JDK 5開始,Java增加了對元數據(MetaData)的支持,也就是Annotation(注釋)。Annotation提供了一種為程序元素設置元數據的方法,從某些方面來看,Annotation就想修飾符一樣,可用于修飾包、類、構造器、方法、成員變量、參數、局部變量的聲明,這些信息被存儲在Annotation的”name = value”對中
Annotation是一個接口,程序可以通過反射來獲取指定程序元素的Annotation對象,然后通過Annotation對象來取得注釋里的元數據
Annotation能被用來為程序元素(類、方法、成員變量等)設置元數據,且不會影響程序代碼的執行,無論增加、刪除Annotation,代碼都始終如一地執行。如果希望讓程序中的Annotation在運行時起一定的作用,只有通過某種配套的工具對Annotation中的信息進行訪問和處理,訪問和處理Annotation的工具統稱為APT(Annotation Processing Tool)
基本Annotation使用Annotation時要在其前面增加@符號,并把該Annotation當成一個修飾符使用,用于修飾它支持的程序元素
5個基本的Annotation
@Override
@Deprecated
@SuppressWarnings
@SafeVarargs
@FunctionalInterface
限定重寫父類方法:@Override@Override 就是用來指定方法覆載,它可以強制一個子類必須覆蓋父類的方法。@Override的作用是告訴編譯器檢查這個方法,保證父類要包含一個被該方法重寫的方法,否則就會編譯出錯。@Override主要是幫助程序員避免一些低級錯誤,如重寫info()方法,卻手誤寫成了inf(),編譯器不會報錯,你可能找半天才能找到錯誤
@Override 只能修飾方法,不能修飾其他程序元素
標示已過時:@Deprecated@Deprecated 用于表示某個程序元素(類,方法等)已過時,當其他程序使用已過時的類,方法時,編譯器將會給出警告
抑制編譯器警告:@SuppressWarnings@SuppressWarnings 指示被該Annotation修飾的程序元素(以及該程序元素中的所有子元素)取消顯示指定的編譯器警告。@SuppressWarnings 會一直作用域該程序元素的所有子元素,例如,使用@SuppressWarnings修飾某個類取消顯示某個編譯器警告,同時又修飾該類里的某個方法取消顯示另一個編譯器警告,那么該方法將會同時取消顯示這兩個編譯器警告
Java 7的“堆污染”警告與@SafeVarargsList list = new ArrayList(); list.add(10); //添加元素時引發unchecked異常 // 下面代碼引起“未經檢查的轉換”的警告,但編譯、運行時完全正常 List temp = list; // ① // 但只要訪問temp里的元素,就會引起運行時異常 System.out.println(temp.get(0));
“堆污染”(Heap pollution):當把一個不帶泛型的對象賦給一個帶泛型的變量時,往往就會方式這種“堆污染”
Java會在定義該方法時就發出“堆污染”警告,這樣保證開發者“更早”地注意到程序中可能存在的“漏洞”。有些時候,開發者不希望看到這個警告,則可以使用如下三種方式來“抑制”這個警告
使用@SafeVarargs 修飾引發該警告的方法或構造器
使用@SuppressWarnings("unchecked")修飾
編譯時使用-Xlint:varargs選項(很少使用)
Java 8的函數式接口與@FunctionalInterfaceJava 8規定:如果接口中只有一個抽象方法(可以包含多個默認方法或多個static方法),該接口就是函數式接口。該注解只能夠修飾接口,不能夠修飾其他程序元素。@FunctionalInterface 只是告訴編譯器檢查這個接口,保證該接口只能包含一個抽象方法,否則就會編譯出錯
@FunctionalInterface 只能修飾接口,不能修飾其他程序元素
JDK的元AnnotationJDK除了在java.lang下提供了5個基本的Annotation之外,還在java.lang.annotation包下提供了6個Meta Annotation,其中有5個元Annotation都用于修飾其他的Annotation定義。其中@Repeatable專門用于定義Java 8新增的重復注解
使用@Retention@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
@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
@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
@Inherited 元Annotation指定被它修飾的Annotation將具有繼承性——如果某個類使用了@Xxx注解(定義該Annotation時使用了@Inherited修飾)修飾,則其子類將自動被@Xxx修飾
自定義Annotation 定義Annotation定義一個新的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不會自己生效,必須由開發者提供相應的工具來提取并處理Annotation信息
AnnotatedElement接口是所有程序元素(如Class、Method、Constructor等)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象(如Class、Method、Constructor等)之后,程序就可以調用該對象的如下幾個方法來訪問Annotation信息
Annotation[] getAnnotations():返回該程序元素上存在的所有注解,若沒有注解,返回長度為0的數組
Annotation[] getDeclaredAnnotations():返回直接修飾該程序元素的所有Annotation
boolean isAnnotationPresent(Class annotationClass) :判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false
// 獲取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的示例 e.g. One
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) + "個方法,其中: " + "失敗了:" + failed + "個, " + "成功了:" + passed + "個!"); } }
public class RunTests { public static void main(String[] args) throws Exception { // 處理MyTest類 ProcessorTest.process("MyTest"); } }e.g. Two
通過使用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新增的重復注解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) { Classclazz = 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新增的Type Annotation
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){} }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66530.html
摘要:定義注解,也叫元數據。它可以聲明在包類字段方法局部變量方法參數等的前面,用來對這些元素進行說明,注釋。方法返回該程序元素上存在的所有注解。與此接口中的其他方法不同,該方法將忽略繼承的注釋。 定義: 注解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK5.0及以后版本引入的一個特性,與類、接口、枚舉是在同一個層次。 它可以聲明在包、類、字段、方法、局部變量、方法參數等...
摘要:注解提供了一種安全的類似注釋的機制,用來將任何的信息或元數據與程序元素類方法成員變量等進行關聯。為程序的元素類方法成員變量加上更直觀更明了的說明,這些說明與程序的業務邏輯無關,并且提供給指定的工具或框架使用。 什么是注解? Annotation 是 Java5 之后開始引入的新特性,中文為注解。注解提供了一種安全的類似注釋的機制,用來將任何的信息或元數據(metadata)與程序元素(...
摘要:的在日常開發,特別是開發中使用廣泛,各種框架,測試框架多多少少都會引入一些注解。的功能是作用于程序元數據的特殊類型。而在編譯期使用注解則需要特殊的工具,本文不討論。可以看到通過類中的反射方法獲取到了這個類的注解以及其方法的注解。 Java的Annotation在日常開發,特別是java web開發中使用廣泛,各種web框架,測試框架多多少少都會引入一些注解。若對java注解有一個全面深...
摘要:另一個很重要的因素是定義了一種標準的描述元數據的方式。對于注解,它的用戶就是虛擬機,工作在字節碼層面,在編譯階段進行檢查,其處理機制主要是內部處理。 什么是注解 用一個詞就可以描述注解,那就是元數據,即一種描述數據的數據。所以,可以說注解就是源代碼的元數據。比如,下面這段代碼: @Override public String toString() { return This is St...
閱讀 632·2021-11-22 15:32
閱讀 2720·2021-11-19 09:40
閱讀 2313·2021-11-17 09:33
閱讀 1263·2021-11-15 11:36
閱讀 1864·2021-10-11 10:59
閱讀 1475·2019-08-29 16:41
閱讀 1780·2019-08-29 13:45
閱讀 2150·2019-08-26 13:36