在 Java 8 之前的版本中,隻能允許在聲明式前使用 Annotation。而在 Java 8 版本中,Annotation 可以被用在任何使用 Type 的地方,例如:初始化對象時 (new),對象類型轉化時,使用 implements 表達式時,或者使用 throws 表達式時。
//初始化對象時
String myString = new @NotNull String();
//對象類型轉化時
myString = (@NonNull String) str;
//使用 implements 表達式時
class MyList<T> implements @ReadOnly List<@ReadOnly T>{
...
}
//使用 throws 表達式時
public void validateValues() throws @Critical ValidationFailedException{
...
}
定義一個 Type Annotation 的方法與普通的 Annotation 類似,隻需要指定 Target 為 ElementType.TYPE_PARAMETER 或者 ElementType.TYPE_USE,或者同時指定這兩個 Target。
@Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
public @interface MyAnnotation {
}
ElementType.TYPE_PARAMETER 表示這個 Annotation 可以用在 Type 的聲明式前,而 ElementType.TYPE_USE 表示這個 Annotation 可以用在所有使用 Type 的地方(如:泛型,類型轉換等)
與 Java 8 之前的 Annotation 類似的是,Type Annotation 也可以通過設定 Retention 在編譯後保留在 class 檔案中(RetentionPolicy.CLASS)或者運作時可通路(RetentionPolicy.RUNTIME)。但是與之前不同的是,Type Annotation 有兩個新的特性:在本地變量上的 Annotation 可以保留在 class 檔案中,以及泛型類型可以被保留甚至在運作時被通路。
雖然 Type Annotation 可以保留在 class 檔案中,但是它并不會改變程式代碼本身的行為。例如在一個方法前加上 Annotation,調用此方法傳回的結果和不加 Annotation 的時候一緻。
Java 8 通過引入 Type Annotation,使得開發者可以在更多的地方使用 Annotation,進而能夠更全面地對代碼進行分析以及進行更強的類型檢查。
TYPE_USE Annotation
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber1 {
}
@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber2 {
}
@Target({ ElementType.TYPE_USE })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber3 {
}
定義如上三個注解,則在一個類中定義一個域時,可以有如下這種方式;
@PrimeNumber1
private @PrimeNumber2 String anInt = new @PrimeNumber3 String();
其中在PrimeNumber1和PrimeNumber2 這兩個位置是一樣的效果,這個一樣的效果是說,同一個注解放在PrimeNumber1處還是PrimeNumber2處,作用是一樣的,用Java Reflect API的時候(Field.getAnnotations()或者Field.getAnnotatedType().getAnnotations()),注解放在這兩個位置都能擷取到;
Field.getAnnotations()是用來擷取Target為ElementType.FIELD的注解;
Field.getAnnotatedType().getAnnotations()用來擷取Target為ElementType.TYPE_USE的注解;
TYPE_PARAMETER Annotation
public class GetAnnotatedTypeExample2<@PrimeNumber4 T> {
public static void main(String... args) throws NoSuchFieldException {
TypeVariable<Class<GetAnnotatedTypeExample2>>[] tv = GetAnnotatedTypeExample2.class.getTypeParameters();
System.out.println(tv[0].getAnnotations()[0].toString());
}
}
@Target({ ElementType.TYPE_PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface PrimeNumber4 {
}
類型注解的作用
類型注解被用來支援在Java的程式中做強類型檢查。配合第三方插件工具Checker Framework(注:此插件so easy,這裡不介紹了),可以在編譯的時候檢測出runtime error(eg:UnsupportedOperationException; NumberFormatException;NullPointerException異常等都是runtime error),以提高代碼品質。這就是類型注解的作用。
Note:
使用Checker Framework可以找到類型注解出現的地方并檢查。
eg:
import checkers.nullness.quals.*;
public class TestDemo{
void sample() {
@NonNull Object my = new Object();
}
}
使用javac編譯上面的類:(當然若下載下傳了Checker Framework插件就不需要這麼麻煩了)
javac -processor checkers.nullness.NullnessChecker TestDemo.java
上面編譯是通過的,但若修改代碼:
@NonNull Object my = null;
但若不想使用類型注解檢測出來錯誤,則不需要processor,正常javac TestDemo.java是可以通過編譯的,但是運作時會報 NullPointerException 異常。
為了能在編譯期間就自動檢查出這類異常,可以通過類型注解結合 Checker Framework 提前排查出來錯誤異常。
注意java 5,6,7版本是不支援注解@NonNull,但checker framework 有個向下相容的解決方案,就是将類型注解@NonNull 用/**/注釋起來。
import checkers.nullness.quals.*;
public class TestDemo{
void sample() {
/*@NonNull*/ Object my = null;
}
}