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 二個版本,但大致上概念是相通的。
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 上面囉!
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
Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786