自訂標註


每個標註都會有個標註型態(Annotation type),所有標註型態其實都是 java.lang.annotation.Annotation子介面,@Override的標註型態為java.lang.Override@Deprecated的標註型態為java.lang.Deprecated等,常用標準標註 中介紹的標註型態,都位於java.lang套件中。

你可以自訂標註,先來看看如何定義標示標註(Marker Annotation),也就是標註名稱本身就是資訊,對編譯器或應用程式來說,主要是檢查是否有標註出現,並作出對應的動作,例如@Override的作用就是標示標註。要定義一個標註可以使用@interface。例如:

package cc.openhome;
public @interface Test {}

編譯完成後,就可以在程式碼中使用@Test標註了。例如:

public class SomeTestCase {
    @Test
    public void testDoSome() {
        ...略
    }
}

如果標註名稱本身無法提供足夠資訊,可進一步設定單值標註(Single-value Annotation)。例如:

package cc.openhome;
public @interface Test2 {
    int timeout(); 
}

這表示標註將會有個timeout屬性可以設定int值。例如:

@Test2(timeout = 10)
public void testDoSome2() {
    ...
}

標註屬性也可以用陣列形式指定。例如如下定義標註的話:

package cc.openhome;
public @interface Test3 {
    String[] args(); 
}

就可以用陣列形式指定屬性:

@Test3(args = {"arg1", "arg2"})
public void testDoSome3() {
    ...
}

在定義標註屬性時,如果屬性名稱為value,則可以省略屬性名稱,直接指定值。例如:

package cc.openhome;
public @interface Ignore {
    String value();
}

這個標註可以使用@Ignore(value = "message")指定,也可以使用@Ignore("message")指定,而以下這個標註:

package cc.openhome;
public @interface TestClass {
    Class[] value();
}

可以使用@TestClass(value = {Some.class, Other.class})指定,也可以使用@TestClass({Some.class, Other.class})指定。
也可以對成員設定預設值,使用default關鍵字即可。例如:

package cc.openhome;
public @interface Test4 {
    int timeout() default 0;
    String message default "";
}

如此一來,如果設定為@Test4,則timeout屬性預設值就是0,message預設就是空字串,如果設定@Test4(timeout = 10, message = "逾時10秒"),則timeout屬性的值就是10,message就是"逾時10秒"。如果是Class設定的屬性比較特別,default之後不能接上null,會發生編譯錯誤,必須自訂一個類別作為預設值。例如:

package cc.openhome;
public @interface Test5 {
    Class expected() default Default.class;
    class Default {}
}

如果要設定陣列預設值的話,可以在default之後加上{}。例如:

package cc.openhome;
public @interface Test6 {
    String[] args() default {}; 
}

必要時{}中可放置元素值。例如:

package cc.openhome;
public @interface Test7 {
    String[] args() default {"arg1", "arg2"};
}

在定義標註時,可使用java.lang.annotation.Target限定標註使用位置,限定時可指定java.lang.annotation.ElementType的列舉值:

package java.lang.annotation;
public enum ElementType {
    TYPE,                  // 用於類別、介面、列舉等
    FIELD,                 // 用於資料成員
    METHOD,                // 用於方法
    PARAMETER,             // 用於方法上的參數
    CONSTRUCTOR,           // 用於建構式
    LOCAL_VARIABLE,        // 用於區域變數
    ANNOTATION_TYPE,       // 用於標註型態
    PACKAGE,               // 適用套件
    TYPE_PARAMETER,        // 用於泛型宣告,JDK8新增
    TYPE_USE               // 用於各種型態,JDK8新增
}

例如想將@Test8限定只能用於方法:

package cc.openhome;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Target({ElementType.METHOD})
public @interface Test8 {}

嘗試在方法以外的地方加上@Test8就會發生編譯錯誤:

限定標註使用位置


在製作JavaDoc文件時,預設並不會將標註資料加入文件中,如果想要將標註資料加入文件,可以使用java.lang.annotation.Documented。例如:

package cc.openhome;

import java.lang.annotation.Documented;

@Documented
public @interface Test9 {}

如果在文件中使用到@Test9,則產生JavaDoc後,文件中就會包括@Test9的資訊:

在文件中記錄標註資訊


在定義標註型態並使用於程式碼時,預設父類別設定的標註,不會被繼承至子類別,可以在定義標註時設定java.lang.annotation.Inherited標註,就可以讓標註被子類別繼承。例如:

package cc.openhome;

import java.lang.annotation.Inherited;

@Inherited
public @interface Test10 {}

JDK8新增了型態標註(Type Annotations),可以讓你在有型態資訊出現的任何位置進行標註,詳情可參考 Type Annotations Specification,主要就是在ElementType增加了TYPE_PARAMETERTYPE_USE兩個列舉值。舉例而言,如果你想如下標註:

List<@Email String> emails = ...;

那麼在定義Email標註時,@Target可以使用ElementType.TYPE_USE。例如:

package cc.openhome;

import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@Target({ElementType.TYPE_USE})
public @interface Email {}