前序
??????注解是什么?簡(jiǎn)單說注解就是一種標(biāo)注(標(biāo)記、標(biāo)識(shí)),沒有具體的功能邏輯代碼。通過注解開發(fā)人員可以在不改變?cè)写a和邏輯的情況下在源代碼中嵌入補(bǔ)充信息。Kotlin注解的使用和Java完全一樣,聲明注解類的語法略有不同。Java 注解與 Kotlin 100% 兼容。
注解的定義
??????注解可以把額外的元數(shù)據(jù)關(guān)聯(lián)到一個(gè)聲明上,然后元數(shù)據(jù)可以被反射機(jī)制或相關(guān)的源代碼工具訪問。
聲明Kotlin的注解
??????Kotlin的聲明注解的語法和常規(guī)類的聲明非常相似,但需要在class關(guān)鍵字之前加上annotation修飾符。但Kotlin編譯器禁止為注解類指定類主體,因?yàn)樽⒔忸愔皇怯脕矶x關(guān)聯(lián)到 聲明 和 表達(dá)式 的元數(shù)據(jù)的結(jié)構(gòu)。
#daqiKotlin.kt
annotation class daqiAnnotation
Java注解聲明:
#daqiJava.java
public @interface daqiAnnotation {
}
注解的構(gòu)造函數(shù)
注解可以有接受參數(shù)的構(gòu)造函數(shù)。
其中注解的構(gòu)造函數(shù)允許的參數(shù)類型有:
- 對(duì)應(yīng)于 Java 原生類型的類型(Int、 Long等)
- 字符串
- 類(Foo::class)
- 枚舉
- 其他注解
- 上面已列類型的數(shù)組。
注解作為注解構(gòu)造函數(shù)的參數(shù)
當(dāng)注解作為另一個(gè)注解的參數(shù),則其名稱不用以 @ 字符為前綴:
annotation class daqiAnnotation(val str: String)
annotation class daqiAnnotation2(
val message: String,
val annotation: daqiAnnotation = daqiAnnotation(""))
類作為注解構(gòu)造函數(shù)的參數(shù)
當(dāng)需要將一個(gè)類指定為注解的參數(shù),請(qǐng)使用 Kotlin 類 (KClass)。Kotlin 編譯器會(huì)自動(dòng)將其轉(zhuǎn)換為 Java 類,以便 Java 代碼能夠正??吹皆撟⒔饧皡?shù) 。
annotation class daqiAnnotation(val arg1: KClass<*>, val arg2: KClass<out Any>)
@daqiAnnotation(String::class, Int::class) class MyClass
將其反編譯后,可以看到轉(zhuǎn)換為相應(yīng)的Java類:
@daqiAnnotation(
arg1 = String.class,
arg2 = int.class
)
public final class MyClass {
}
注意:注解參數(shù)不能有可空類型,因?yàn)?JVM 不支持將 null 作為注解屬性的值存儲(chǔ)。
Kotlin的元注解
??????和Java一樣,Kotlin的注解類也使用元注解進(jìn)行注解。用于其他注解的注解稱為元注解,可以理解為最基本的標(biāo)注。
??????Kotlin標(biāo)準(zhǔn)庫中定義了4個(gè)元注解,分別是:MustBeDocumented、Repeatable、Retention、Target。
@Target
@Target用于指定可以應(yīng)用該注解的元素類型(類、函數(shù)、屬性、表達(dá)式等)。
查看Target的源碼:
#Annotation.kt
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
@Target注解中可以同時(shí)接收一個(gè)或多個(gè)AnnotationTarget枚舉值:
public enum class AnnotationTarget {
//作用于類(包括枚舉類)、接口、object對(duì)象和注解類
CLASS,
//僅作用于注解類
ANNOTATION_CLASS,
//作用于泛型類型參數(shù)(暫時(shí)不支持)(JDK8)
TYPE_PARAMETER,
//作用于屬性
PROPERTY,
//作用于字段(包括枚舉常量和支持字段)。
FIELD,
//作用于局部變量
LOCAL_VARIABLE,
//作用于函數(shù)或構(gòu)造函數(shù)的參數(shù)
VALUE_PARAMETER,
//作用于構(gòu)造函數(shù)(包括主構(gòu)造函數(shù)和次構(gòu)造函數(shù))
CONSTRUCTOR,
//作用于方法(不包括構(gòu)造函數(shù))
FUNCTION,
//僅作用于屬性的getter函數(shù)
PROPERTY_GETTER,
//僅作用于屬性的setter函數(shù)
PROPERTY_SETTER,
//作用于類型(如方法內(nèi)參數(shù)的類型)
TYPE,
//作用于表達(dá)式
EXPRESSION,
//作用于文件,可配合 file點(diǎn)目標(biāo) 使用: (例如: @file:JvmName("daqiKotlin"))
FILE,
//作用于類型別名
@SinceKotlin("1.1")
TYPEALIAS
}
?????? 注意:Java代碼中無法使用Target為AnnotationTarget.PROPERTY的注解。如果想讓這樣的注解在Java中使用,可以添加多一條AnnotationTarget.FIELD的注解。
@Retention
@Retention 聲明注解的保留策略。
查看Retention的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)
@Retention注解中可以接收一個(gè)AnnotationRetention枚舉值:
public enum class AnnotationRetention {
//表示注解僅保留在源代碼中,編譯器將丟棄該注解。
SOURCE,
//注解將由編譯器記錄在class文件中 但在運(yùn)行時(shí)不需要由JVM保留。
BINARY,
//注解將由編譯器記錄在class文件中,并在運(yùn)行時(shí)由JVM保留,因此可以反射性地讀取它們。(默認(rèn)行為)
RUNTIME
}
?????? 注意:Java的元注解默認(rèn)會(huì)在.class文件中保留注解,但不會(huì)讓它們?cè)谶\(yùn)行時(shí)被訪問到。大多數(shù)注解需要在運(yùn)行時(shí)存在,以至于Kotlin將RUNTIME作為@Retention注解的默認(rèn)值。
@Repeatable
允許在單個(gè)元素上多次使用相同的該注解;
查看Repeatable的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable
?????? 注意:在"嘗試使用"@Repeatable時(shí)發(fā)現(xiàn),該注解必須要在Retention元注解指定為AnnotationRetention.SOURCE時(shí)才能重復(fù)使用,但Java的@Repeatable元注解并沒有該限制。(具體的Java @Repeatable元注解的使用示例可以看這篇文章)。因?yàn)?code>@Repeatable是Java 8引入的新的元注解,而兼容Java 6的Kotlin對(duì)此有點(diǎn)不兼容?
@MustBeDocumented
指定該注解是公有 API 的一部分,并且應(yīng)該包含在生成的 API 文檔中顯示的類或方法的簽名中。
查看MustBeDocumented的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented
消失的@Inherited元注解
??????相對(duì)Java的5個(gè)元注解,Kotlin只提供了與其對(duì)應(yīng)的4個(gè)元注解,Kotlin暫時(shí)不需要支持@Inherited元注解。
??????@Inherited注解表明注解類型可以從超類繼承。具體意思是:存在一個(gè)帶@Inherited元注解的注解類型,當(dāng)用戶在某個(gè)類中查詢?cè)撟⒔忸愋筒⑶覜]有此類型的注解時(shí),將嘗試從該類的超類以獲取注解類型。重復(fù)此過程,直到找到此類型的注解,或者到達(dá)類層次結(jié)構(gòu)(對(duì)象)的頂部為止。如果沒有超類具有此類型的注解,則查詢將指示相關(guān)類沒有此類注解。此注解僅適用于類聲明。
Kotlin預(yù)定義的注解
??????Kotlin為了與Java具有良好的互通性,定義了一系列注解用于攜帶一些額外信息,以便編譯器做兼容轉(zhuǎn)換。
@JvmDefault
將Kotlin接口的默認(rèn)方法生成Java 8的默認(rèn)方法的字節(jié)碼
查看源碼:
@SinceKotlin("1.2")
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class JvmDefault
??????前面接口和類中提到,當(dāng)在Kotlin中聲明一個(gè)帶默認(rèn)方法的接口時(shí),往往會(huì)將這些“默認(rèn)方法”聲明為抽象方法,同時(shí)也會(huì)在該接口中生成一個(gè)DefaultImpls靜態(tài)內(nèi)部類,并在其中定義同名的靜態(tài)方法來提供默認(rèn)實(shí)現(xiàn)。
??????但這樣會(huì)存在一個(gè)問題,當(dāng)對(duì)舊的Kotlin接口添加新的默認(rèn)方法時(shí),實(shí)現(xiàn)該接口的Java類需要重新實(shí)現(xiàn)新添的接口方法,否則會(huì)編譯不通過。同是默認(rèn)方法,但與Java 8引入默認(rèn)方法的初衷相違背。為此,Kotlin提供了@JvmDefault注解。對(duì)標(biāo)有@JvmDefault注解的默認(rèn)方法,編譯器會(huì)將其編譯為Java 8的默認(rèn)接口。
#daqiKotlin.kt
public interface daqiInterface{
@JvmDefault//剛添加會(huì)報(bào)錯(cuò)
fun daqiFunc() = println("帶@JvmDefault的默認(rèn)方法")
fun daqiFunc2() = println("默認(rèn)方法")
}
#java文件
public interface daqiInterface {
@JvmDefault
default void daqiFunc() {
String var1 = "帶@JvmDefault的默認(rèn)方法";
System.out.println(var1);
}
void daqiFunc2();
public static final class DefaultImpls {
public static void daqiFunc2(daqiInterface $this) {
String var1 = "默認(rèn)方法";
System.out.println(var1);
}
}
}
??????當(dāng)你直接添加 @JvmDefault時(shí),編譯器會(huì)報(bào)錯(cuò)。這時(shí)你需要在Gradle中配置以下參數(shù):(具體Kotlin使用Gradle看官網(wǎng))
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default = compatibility']
//freeCompilerArgs = ['-Xjvm-default = enable']
}
}
??????通過@JvmDefault的注解得知,配置時(shí)可以選擇-Xjvm-default = enable或-Xjvm-default = compatibility。這兩個(gè)的區(qū)別是:
-
-Xjvm-default = enable會(huì)從DefaultImpls靜態(tài)內(nèi)部類中刪除對(duì)應(yīng)的方法。 -
-Xjvm-default = compatibility仍會(huì)在DefaultImpls靜態(tài)內(nèi)部類中保留對(duì)應(yīng)的方法,提高兼容性。
注意:只有JVM目標(biāo)字節(jié)碼版本1.8(-jvm-target 1.8)或更高版本才能生成默認(rèn)方法。
@JvmField
指示Kotlin編譯器不為此屬性生成getter / setter并將其修飾為public。
查看源碼:
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmField
??????Kotlin聲明的屬性都默認(rèn)使用private修飾,并提供setter / getter訪問器對(duì)其進(jìn)行訪問。而@JvmField就是告訴編譯器不要為該屬性自動(dòng)創(chuàng)建setter / getter訪問器,并將對(duì)其使用public修飾。(用在伴生對(duì)象的屬性上,可生成public修飾的static屬性)
#daqiKotlin.kt
class Person{
@JvmField
val name:String = ""
}
反編譯后查看源碼中只聲明了一個(gè)public對(duì)象:
#java文件
public final class Person {
@JvmField
@NotNull
public final String name = "";
}
?????? 注意該注解只能用在有幕后字段的屬性上,對(duì)于沒有幕后字段的屬性(例如:擴(kuò)展屬性、委托屬性等)不能使用。因?yàn)橹挥袚碛心缓笞侄蔚膶傩赞D(zhuǎn)換成Java代碼時(shí),才有對(duì)應(yīng)的Java變量。
Kotlin屬性擁有幕后字段需要滿足以下條件之一:
- 使用默認(rèn) getter / setter 的屬性,一定有幕后字段。對(duì)于 var 屬性來說,只要 getter / setter 中有一個(gè)使用默認(rèn)實(shí)現(xiàn),就會(huì)生成幕后字段。
- 在自定義 getter / setter 中使用了 field 的屬性。
@JvmName
指定生成Java類的類名或方法名。
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmName(actual val name: String)
??????根據(jù)注解的聲明,屬性的訪問器getter / setter也可以使用該注解,但屬性不能使用~。
??????在daqiKotlin.kt文件中聲明的所有函數(shù)和屬性(包括擴(kuò)展函數(shù))都被編譯為名為在DaqiKotlinKt的Java類的靜態(tài)方法。其中文件名首字母會(huì)被改為大寫,后置Kt。當(dāng)需要修改該Kotlin文件生成的Java類名稱時(shí),可以使用@JvmName名指定生成特定的文件名:
@file:JvmName("daqiKotlin")
package com.daqi.test
@JvmName("daqiStateFunc")
public fun daqiFunc(){
}
??????反編譯可以看到生成的Java類名稱已經(jīng)修改為daqiKotlin,而非DaqiKotlinKt,同時(shí)頂層函數(shù)daqiFunc的方法名被修改為daqiStateFunc:
public final class DaqiKotlinKt {
@JvmName(name = "daqiStateFunc")
public static final void daqiStateFunc() {
}
}
@JvmMultifileClass
指示Kotlin編譯器生成一個(gè)多文件的類。該文件具有在此文件中聲明的頂級(jí)函數(shù)和屬性。
查看源碼:
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class JvmMultifileClass
??????當(dāng)需要將多個(gè)Kotlin文件中的方法和屬性歸到一個(gè)Java類時(shí),可以在多個(gè)文件中聲明一樣的@JvmName,并在其下面添加@JvmMultifileClass注解。(多個(gè)文件中聲明一樣的@JvmName,但不添加@JvmMultifileClass注解會(huì)編譯不通過)
#daqiKotlin.kt
@file:JvmName("daqiKotlin")
@file:JvmMultifileClass
package com.daqi.test
fun daqi(){
}
#daqiKotlin2.kt
@file:JvmName("daqiKotlin")
@file:JvmMultifileClass
package com.daqi.test
fun daqi2(){
}
??????Kotlin編譯器會(huì)將該兩個(gè)文件中的方法和屬性合并到@JvmName注解生成的指定名稱的Java類中:
@JvmOverloads
指示Kotlin編譯器為此函數(shù)生成替換默認(rèn)參數(shù)值的重載函數(shù)(從最后一個(gè)開始省略每個(gè)參數(shù))。
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmOverloads
??????Java并沒有參數(shù)默認(rèn)值的概念,當(dāng)你從Java中調(diào)用Kotlin的默認(rèn)參數(shù)函數(shù)時(shí),必須顯示地指定所有參數(shù)值。使用@JvmOverloads注解該方法,Kotlin編譯器會(huì)生成相應(yīng)的Java重載函數(shù),從最后一個(gè)參數(shù)開始省略每個(gè)函數(shù)。
#daqiKotlin.kt
@JvmOverloads
fun daqi(name :String = "daqi",age :Int = 2019){
println("name = $name,age = $age ")
}
@JvmStatic
將對(duì)象聲明或伴生對(duì)象的方法或?qū)傩缘脑L問器暴露成一個(gè)同名的Java靜態(tài)方法。
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public actual annotation class JvmStatic
??????對(duì)于Kotlin的對(duì)象聲明和伴生對(duì)象,在Kotlin中可以像靜態(tài)函數(shù)那樣調(diào)用類名.方法名進(jìn)行調(diào)用。但在Java中,需要在這其中添加多一個(gè)Companion或INSTANCE,使調(diào)用很不自然。使用@JvmStatic注解標(biāo)記伴生對(duì)象或?qū)ο舐暶髦械姆椒ê蛯傩?,使其在Java中可以像Kotlin一樣調(diào)用這些方法和屬性。
在Kotlin中定義一個(gè)伴生對(duì)象,并用標(biāo)記@JvmStatic注解:
class daqi{
companion object {
@JvmStatic
val name:String = ""
@JvmStatic
fun daqiFunc(){
}
}
}
??????反編譯可以觀察到 伴生對(duì)象類 或 對(duì)象聲明類 中聲明了屬于它們自己的方法和屬性,但同時(shí)在對(duì)象聲明類本身或伴生對(duì)象類的外部類中也聲明了一樣的靜態(tài)的方法和屬性訪問器供外部直接訪問。
public final class daqi {
@NotNull
private static final String name = "";
public static final daqi.Companion Companion = new daqi.Companion((DefaultConstructorMarker)null);
@NotNull
public static final String getName() {
daqi.Companion var10000 = Companion;
return name;
}
@JvmStatic
public static final void daqiFunc() {
Companion.daqiFunc();
}
public static final class Companion {
@JvmStatic
public static void name$annotations() {
}
@NotNull
public final String getName() {
return daqi.name;
}
@JvmStatic
public final void daqiFunc() {
}
}
}
??????所以,如果對(duì)象聲明和伴生對(duì)象需要和Java層進(jìn)行比較頻繁的交互時(shí),建議還是加上@JvmStatic
@JvmSuppressWildcards 和 @JvmWildcard
@JvmSuppressWildcards指示編譯器為泛型參數(shù)生成或省略通配符。(默認(rèn)是省略)
@JvmWildcard指示編譯器為為泛型參數(shù)生成通配符。
查看源碼:
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmSuppressWildcards(actual val suppress: Boolean = true)
--------------------------------------------------------------------------
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmWildcard
@PurelyImplements
指示Kotlin編譯器將帶該注釋的Java類視為給定Kotlin接口的純實(shí)現(xiàn)?!癙ure”在這里表示類的每個(gè)類型參數(shù)都成為該接口的非平臺(tái)類型參數(shù)。
查看源碼:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public annotation class PurelyImplements(val value: String)
??????Kotlin對(duì)來自Java的變量會(huì)當(dāng)作平臺(tái)類型來處理,由開發(fā)者覺得其是可空還是非空。但即便將其聲明為非空,但其實(shí)他還是能接收空值或者返回空值。
#java文件
class MyList<T> extends AbstractList<T> { ... }
#kotlin文件
MyList<Int>().add(null) // 編譯通過
??????但可以借助@PurelyImplements注解,并攜帶對(duì)應(yīng)的Kotlin接口。使其與Kotlin接口對(duì)應(yīng)的類型參數(shù)不被當(dāng)作平臺(tái)類型來處理。
#java文件
@PurelyImplements("kotlin.collections.MutableList")
class MyPureList<T> extends AbstractList<T> { ... }
MyPureList<Int>().add(null) // 編譯不通過
MyPureList<Int?>().add(null) // 編譯通過
@Throws
等價(jià)于Java的throws關(guān)鍵字
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
public annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)
例子
@Throws(IOException::class)
fun daqi() {
}
@Strictfp
等價(jià)于Java的strictfp關(guān)鍵字
查看源碼:
@Target(FUNCTION, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER, CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Strictfp
@Transient
等價(jià)于Java的transient關(guān)鍵字
查看源碼:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Transient
@Synchronized
等價(jià)于Java的synchronized關(guān)鍵字
查看源碼:
@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Synchronized
@Volatile
等價(jià)于Java的volatile關(guān)鍵字
查看源碼:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Volatile
點(diǎn)目標(biāo)聲明
??????許多情況下, Kotlin代碼中的單個(gè)聲明會(huì)對(duì)應(yīng)成多個(gè) Java 聲明 ,而且它們每
個(gè)都能攜帶注解。例如, Kotlin 屬性就對(duì)應(yīng)了 Java 宇段、 getter ,以
及一個(gè)潛在的 setter。這時(shí)需要使用點(diǎn)目標(biāo)指定說明注解用在什么地方。
點(diǎn)目標(biāo)聲明被用來說明要注解的元素。使用點(diǎn)目標(biāo)被放在@符號(hào)和注解名
稱之間,并用冒號(hào)和注解名稱隔開。
點(diǎn)目標(biāo)的完整列表如下:
- property————Java 的注解不能應(yīng)用這種使用點(diǎn)目標(biāo)
- field————為屬性生成的字段
- get ————屬性的 getter
- set ————屬性的 setter
- receiver ————擴(kuò)展函數(shù)或者擴(kuò)展屬性的接收者參數(shù)。
- param————構(gòu)造方法的參數(shù)。
- setparam————屬性 setter 的參數(shù)
- delegate ————為委托屬性存儲(chǔ)委托實(shí)例的字段
- file ———— 包含在文件中聲明的頂層函數(shù)和屬性的類。
//注解的是get方法,而不是屬性
@get:daqiAnnotation
val daqi:String = ""
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class daqiAnnotation()
參考資料:
- 《Kotlin實(shí)戰(zhàn)》
- Kotlin官網(wǎng)
android Kotlin系列:
Kotlin知識(shí)歸納(一) —— 基礎(chǔ)語法
Kotlin知識(shí)歸納(二) —— 讓函數(shù)更好調(diào)用
Kotlin知識(shí)歸納(三) —— 頂層成員與擴(kuò)展
Kotlin知識(shí)歸納(六) —— 類型系統(tǒng)
Kotlin知識(shí)歸納(十一) —— 高階函數(shù)