13. Annotation

Annotation 是種可以加註在程式碼物件上的一種標記物件(metadata of code),是的,聽起來非常單純,相信大家每天都會使用到但卻幾乎忘了它的存在,其實很多很功能強大的 library 都有利用 annotation 來完成某部分的功能,像是我們昨天提到的 retrofit、gson 以及我們之後會介紹的 dagger 等 。

Annotation 到底有什麼厲害之處,我們又可以怎麼使用呢?我們先來看看一個簡單的 annotation 的定義。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

Override 是 Java 提供讓我們在 class 間繼承複寫的 function 比較不會出錯的一個 annotation,以下是我們怎麼使用的例子:

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    //...
}

使用上要以 @ 開頭來表示 annotation 的引用,眼尖的大家會疑問為什麼我們用 java 來舉例呢,如果大家回頭看自己的 kotlin 版本的 onCreate 會發現長這個樣子:

override fun onCreate(savedInstanceState: Bundle?) {
    //...
}

override 在 kotlin 被內建成一個關鍵字了,而且是強制不可省略的,所以當然也沒有必要再使用 Java 版本的 @Override 了。(謎之聲:@Override 你被 override 了)

我們在看另一個常使用的 annotation - Deprecated,有意思的是你在 Java 跟 kotlin 二種不同檔案下會連結到二個不同的 class 如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.METHOD, ElementType.PACKAGE, ElementType.PARAMETER, ElementType.TYPE})
public @interface Deprecated {
}
@Target(CLASS, FUNCTION, PROPERTY, ANNOTATION_CLASS, CONSTRUCTOR, PROPERTY_SETTER, PROPERTY_GETTER, TYPEALIAS)
@MustBeDocumented
public annotation class Deprecated(
    val message: String,
    val replaceWith: ReplaceWith = ReplaceWith(""),
    val level: DeprecationLevel = DeprecationLevel.WARNING
)

對比下我們會發現 kotlin 的 annotation 定義是 annotation class,然後二邊的 annotation 的定義通常也會搭配其他的 annotation 如 @Target@Retention,回頭翻文件會發現以下的定義:

Target 跟 Retention 也有 Java 跟 kotlin 二個版本,但大致上概念是相通的。

  • Target:

    指定被標注的 annotation 可以合法的作用在哪些程式碼物件上的 annotation,有以下這些合法的值:

    public enum class AnnotationTarget {
      /** Class, interface or object, annotation class is also included */
      CLASS,
      /** Annotation class only */
      ANNOTATION_CLASS,
      /** Generic type parameter (unsupported yet) */
      TYPE_PARAMETER,
      /** Property */
      PROPERTY,
      /** Field, including property's backing field */
      FIELD,
      /** Local variable */
      LOCAL_VARIABLE,
      /** Value parameter of a function or a constructor */
      VALUE_PARAMETER,
      /** Constructor only (primary or secondary) */
      CONSTRUCTOR,
      /** Function (constructors are not included) */
      FUNCTION,
      /** Property getter only */
      PROPERTY_GETTER,
      /** Property setter only */
      PROPERTY_SETTER,
      /** Type usage */
      TYPE,
      /** Any expression */
      EXPRESSION,
      /** File */
      FILE,
      /** Type alias */
      @SinceKotlin("1.1")
      TYPEALIAS
    }

    所以如果一個 annotation 被標注了 CLASS@Target 那就代表這個 annotation 只能被標注在 class 上面囉!

  • Retention:

    指定 annotation 的生命週期,有以下這三種值:

    public enum class AnnotationRetention {
      /** Annotation isn't stored in binary output */
      SOURCE,
      /** Annotation is stored in binary output, but invisible for reflection */
      BINARY,
      /** Annotation is stored in binary output and visible for reflection (default retention) */
      RUNTIME
    }

    SOURCE 代表 compile 之後這個 annotation 不會保留,BINARY 代表會存在 class 裡但不能使用,RUNTIME 不只存在 class 還可以透過 reflection 拿到這些資訊。

我們可以找些 annotation 來驗證一下我們是否真的了解,回頭看一下我們上一張所提到 Retrofit 的 @GET@GET 被標注了 @Target(METHOD),所以只能加在 function 的程式碼上,而 @Retention(RUNTIME) 代表我們可以在 runtime 時拿回這個 annotation,所以 Retrofit 才能在 runtime 時依據我們的 annotation 參數建構出一個 http 連線。

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface GET {
  String value() default "";
}

另一個例子是之前提到的 @Override,它的 retention 只有 SOURCE,所以 compile 之後這個資訊就會消失,這也非常合理 因為我們只需要在寫 code 的時候參考 Override,順利 compile 之後有沒有這個 annotation 就不是這麼重要了,是吧。

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

以上就是今天的內容囉,明天我們繼續聊聊怎麼使用 annotation processing。

參考資料: https://kotlinlang.org/docs/reference/annotations.html https://docs.oracle.com/javase/tutorial/java/annotations/basics.html

Last updated

Was this helpful?