Kotlin 的注解
從這篇文章我們一起來看下 Kotlin 中的注解。Kotlin 中的注解是 100% 與 Java 注解兼容的,有很多相同的地方,但是也有一些不同的地方。一起來瞅瞅吧~
1. 注解的本質(zhì)
注解實際上就是一種代碼標簽,它作用的對象是代碼。它可以給特定的注解代碼標注一些額外的信息。然而這些信息可以選擇不同保留時期,比如源碼期、編譯期、運行期。然后在不同時期,可以通過某種方式獲取標簽的信息來處理實際的代碼邏輯,這種方式常常就是我們所說的反射。
2. 注解的定義
在 Kotlin 中注解核心概念和 Java 一樣,注解就是為了給代碼提供元數(shù)據(jù)。并且注解是不直接影響代碼的執(zhí)行。一個注解允許你把額外的元數(shù)據(jù)關(guān)聯(lián)到一個聲明上,然后元數(shù)據(jù)就可以被某種方式(比如運行時反射方式以及一些源代碼工具)訪問。
3. 注解的聲明
在 Kotlin 中的聲明注解的方式和 Java 稍微不一樣,在 Java 中主要是通過 @interface關(guān)鍵字來聲明,而在Kotlin 中只需要通過 annotation class 來聲明, 需要注意的是在 Kotlin 中編譯器禁止為注解類指定類主體,因為在 Kotlin 中注解只是用來定義關(guān)聯(lián)的聲明和表達式的元數(shù)據(jù)的結(jié)構(gòu)。
- Kotlin 注解聲明
package com.mikyou.annotation
//和一般的聲明很類似,只是在class前面加上了annotation修飾符
annotation class TestAnnotation(val value: String)
- Java 注解聲明
package com.mikyou.annotation;
//java中的注解通過@interface關(guān)鍵字進行定義,它和接口聲明類似,只不過在前面多加@
public @interface TestAnnotation {
String value();
}
4. 注解的應(yīng)用
在上一步我們知道了如何聲明和定義標簽了,那么接下來就是用這個標簽,如何把我們定義好的標簽貼到指定的代碼上。在 Kotlin 中使用注解和 Java 一樣。要應(yīng)用一個注解都是 @注解類名。
@Target(AnnotationTarget.FUNCTION)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class TestAnnotation(val value: Int)//和一般的聲明很類似,只是在class前面加上了annotation修飾符
class Test {
@TestAnnotation(value = 1000)
fun test() {//給test函數(shù)貼上TestAnnotation標簽(添加TestAnnotation注解)
//...
}
}
在很多常見的 Java 或 Kotlin 框架中大量使用了注解,比如我們最常見的 JUnit 單元測試框架:
class ExampleUnitTest {
@Test //@Test注解就是為了告訴JUnit框架,這是一個測試方法,當做測試調(diào)用。
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
在 Kotlin 中注解類中還可以擁有注解類作為參數(shù),不妨來看下 Kotlin 中對 @Deprecated這個注解源碼定義,以及它的使用。@Deprecated 注解在原來的 Java 基礎(chǔ)增強了一個 ReplaceWith 功能.??梢灾苯釉谑褂昧死系?API 時,編譯器可以根據(jù) ReplaceWith 中的新 API,自動替換成新的 API。這一點在 Java 中是做不到的,你只能點擊進入這個 API 查看源碼來正確使用新的 API。
//@Deprecated注解比Java多了ReplaceWith功能, 這樣當你在調(diào)用remove方法,編譯器會報錯。使用代碼提示會自動IntelliJ IDEA不僅會提示使用哪個函數(shù)提示替代它,而且會快速自動修正。
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"), level = DeprecationLevel.ERROR)//定義的級別是ERROR級別的,這樣當你在調(diào)用remove方法,編譯器會報錯。
@kotlin.internal.InlineOnly
public inline fun <T> MutableList<T>.remove(index: Int): T = removeAt(index)
@Deprecated 注解的 remove 函數(shù)使用:
//Deprecated注解的使用
fun main(args: Array<String>) {
val list = mutableListOf("a", "b", "c", "d", "e")
list.remove(3)//這里會報錯, 通過remove函數(shù)注解定義,這個remove函數(shù)在定義的level是ERROR級別的,所以編譯器直接拋錯
}
最后來看下 @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(""),//注解類中構(gòu)造器可以使用注解類作為函數(shù)參數(shù)
val level: DeprecationLevel = DeprecationLevel.WARNING
)
@Target()
@Retention(BINARY)
@MustBeDocumented
public annotation class ReplaceWith(val expression: String, vararg val imports: String)
注意:注解類中只能擁有如下類型的參數(shù): 基本數(shù)據(jù)類型、字符串、枚舉、類引用類型、其他的注解類(例如Deprecated注解類中的ReplaceWith注解類)
5. Kotlin 中的元注解
和 Java 一樣在 Kotlin 中,一個 Kotlin 注解類自己本身也可以被注解,可以給注解類加注解。我們把這種注解稱為元注解,可以把它理解為一種基本的注解,也可以把它理解為一種特殊的標簽,用于標注標簽的標簽。
Kotlin 中的元注解類定義于 kotlin.annotation
包中,主要有: @Target、@Retention、@Repeatable、@MustBeDocumented 4 種元注解, 相比 Java 中 5 種元注解: @Target、@Retention、@Repeatable、@Documented、**@Inherited **少了 @Inherited元注解。
5.1 @Target 元注解
介紹
Target 顧名思義就是目標對象,也就是這個標簽作用于哪些代碼中目標對象,可以同時指定多個作用的目標對象。
源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//可以給標簽自己貼標簽
@MustBeDocumented
//注解類構(gòu)造器參數(shù)是個vararg不定參數(shù)修飾符,所以可以同時指定多個作用的目標對象
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
@Target 元注解作用的目標對象
在@Target注解中可以同時指定一個或多個目標對象,那么到底有哪些目標對象呢?這就引出另外一個AnnotationTarget 枚舉類:
public enum class AnnotationTarget {
CLASS, //表示作用對象有類、接口、object對象表達式、注解類
ANNOTATION_CLASS,//表示作用對象只有注解類
TYPE_PARAMETER,//表示作用對象是泛型類型參數(shù)(暫時還不支持)
PROPERTY,//表示作用對象是屬性
FIELD,//表示作用對象是字段,包括屬性的幕后字段
LOCAL_VARIABLE,//表示作用對象是局部變量
VALUE_PARAMETER,//表示作用對象是函數(shù)或構(gòu)造函數(shù)的參數(shù)
CONSTRUCTOR,//表示作用對象是構(gòu)造函數(shù),主構(gòu)造函數(shù)或次構(gòu)造函數(shù)
FUNCTION,//表示作用對象是函數(shù),不包括構(gòu)造函數(shù)
PROPERTY_GETTER,//表示作用對象是屬性的getter函數(shù)
PROPERTY_SETTER,//表示作用對象是屬性的setter函數(shù)
TYPE,//表示作用對象是一個類型,比如類、接口、枚舉
EXPRESSION,//表示作用對象是一個表達式
FILE,//表示作用對象是一個File
@SinceKotlin("1.1")
TYPEALIAS//表示作用對象是一個類型別名
}
5.2 @Retention 元注解
介紹
Retention 對應(yīng)的英文意思是保留期,當它應(yīng)用于一個注解上表示該注解保留存活時間,不管是Java還是Kotlin 一般都有三種時期: 源代碼時期(SOURCE)、編譯時期(BINARY)、運行時期(RUNTIME)。
源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象是注解類
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)//接收一個參數(shù),該參數(shù)有個默認值,默認是保留在運行時期
@Retention 元注解的取值
@Retention 元注解取值主要來源于 AnnotationRetention 枚舉類
public enum class AnnotationRetention {
SOURCE,//源代碼時期(SOURCE): 注解不會存儲在輸出class字節(jié)碼中
BINARY,//編譯時期(BINARY): 注解會存儲出class字節(jié)碼中,但是對反射不可見
RUNTIME//運行時期(RUNTIME): 注解會存儲出class字節(jié)碼中,也會對反射可見, 默認是RUNTIME
}
5.3 @MustBeDocumented元注解
介紹
該注解比較簡單主要是為了標注一個注解類作為公共API的一部分,并且可以保證該注解在生成的API文檔中存在。
源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是注解類
public annotation class MustBeDocumented
5.4 @Repeatable元注解
介紹
這個注解決定標注的注解在一個注解在一個代碼元素上可以應(yīng)用兩次或兩次以上。
源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是注解類
public annotation class Repeatable
5.5 為啥Kotlin去掉了Java中的@Inherited元注解
Java 中的 @Inherited 元注解介紹
Inheried 顧名思義就是繼承的意思,但是這里需要注意并不是表示注解類可以繼承,而是如果一個父類被貼上 @Inherited 元注解標簽,那么它的子類沒有任何注解標簽的話,這個子類就會繼承來自父類的注解。類似下面的例子:
@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}
@TestAnnotation
class Animal {
//...
}
class Cat extends Animal{//也會擁有來自父類Animal的@TestAnnotation注解
//...
}
Kotlin 為啥不需要 @Inherited 元注解?
關(guān)于這個問題實際上在 Kotlin 官網(wǎng)的 discuss 中就有人提出了這個問題,具體感興趣的可以去看看:Inherited annotations and other reflections enchancements。
這里大概說下原因,我們都知道在 Java 中,無法找到子類方法是否重寫了父類的方法。因此不能繼承父類方法的注解。然而 Kotlin 目前不需要支持這個 @Inherited 元注解,因為 Kotlin 可以做到,如果反射提供了override
標記而且很容易做到。
6. 注解的使用場景
- 提供信息給編譯器: 編譯器可以利用注解來處理一些,比如一些警告信息,錯誤等;
- 編譯階段時處理: 利用注解信息來生成一些代碼,在Kotlin生成代碼非常常見,一些內(nèi)置的注解為了與Java API的互操作性,往往借助注解在編譯階段生成一些額外的代碼;
- 運行時處理: 某些注解可以在程序運行時,通過反射機制獲取注解信息來處理一些程序邏輯。