天天看點

Java Type Annotation

在 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; 
} 
}