Java 註釋 Annotation

從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時要在其前面增長@符號,並把該Annotation當成一個修飾符使用,用於修飾它支持的程序元素數組

5個基本的Annotation框架

限定重寫父類方法:@Override

@Override 就是用來指定方法覆載,它能夠強制一個子類必須覆蓋父類的方法。@Override的做用是告訴編譯器檢查這個方法,保證父類要包含一個被該方法重寫的方法,不然就會編譯出錯。@Override主要是幫助程序員避免一些低級錯誤,如重寫info()方法,卻手誤寫成了inf(),編譯器不會報錯,你可能找半天才能找到錯誤

@Override 只能修飾方法,不能修飾其餘程序元素

標示已過期:@Deprecated

@Deprecated 用於表示某個程序元素(類,方法等)已過期,當其餘程序使用已過期的類,方法時,編譯器將會給出警告

抑制編譯器警告:@SuppressWarnings

@SuppressWarnings 指示被該Annotation修飾的程序元素(以及該程序元素中的全部子元素)取消顯示指定的編譯器警告。@SuppressWarnings 會一直做用域該程序元素的全部子元素,例如,使用@SuppressWarnings修飾某個類取消顯示某個編譯器警告,同時又修飾該類裏的某個方法取消顯示另外一個編譯器警告,那麼該方法將會同時取消顯示這兩個編譯器警告

Java 7的「堆污染」警告與@SafeVarargs

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的函數式接口與@FunctionalInterface

Java 8規定:若是接口中只有一個抽象方法(能夠包含多個默認方法或多個static方法),該接口就是函數式接口。該註解只可以修飾接口,不可以修飾其餘程序元素。@FunctionalInterface 只是告訴編譯器檢查這個接口,保證該接口只能包含一個抽象方法,不然就會編譯出錯

@FunctionalInterface 只能修飾接口,不能修飾其餘程序元素

JDK的元Annotation

JDK除了在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信息

  • <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的示例

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)
            + "個方法,其中:\n" + "失敗了:" + failed + "個,\n"
            + "成功了:" + 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)
    {
        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新增的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){}
}
相關文章
相關標籤/搜索