註解是什麼?簡單說註解就是一種標註(標記、標識),沒有具體的功能邏輯代碼。經過註解開發人員能夠在不改變原有代碼和邏輯的狀況下在源代碼中嵌入補充信息。Kotlin註解的使用和Java徹底同樣,聲明註解類的語法略有不一樣。Java 註解與 Kotlin 100% 兼容。html
註解能夠把額外的元數據關聯到一個聲明上,而後元數據能夠被反射機制或相關的源代碼工具訪問。java
Kotlin的聲明註解的語法和常規類的聲明很是類似,但須要在class
關鍵字以前加上annotation
修飾符。但Kotlin編譯器禁止爲註解類指定類主體,由於註解類只是用來定義關聯到 聲明 和 表達式 的元數據的結構。android
#daqiKotlin.kt
annotation class daqiAnnotation
複製代碼
Java註解聲明:數組
#daqiJava.java
public @interface daqiAnnotation {
}
複製代碼
註解能夠有接受參數的構造函數。bash
其中註解的構造函數容許的參數類型有:jvm
當註解做爲另外一個註解的參數,則其名稱不用以 @
字符爲前綴:函數
annotation class daqiAnnotation(val str: String)
annotation class daqiAnnotation2(
val message: String,
val annotation: daqiAnnotation = daqiAnnotation(""))
複製代碼
當須要將一個類指定爲註解的參數,請使用 Kotlin 類 (KClass)。Kotlin 編譯器會自動將其轉換爲 Java 類,以便 Java 代碼可以正常看到該註解及參數 。工具
annotation class daqiAnnotation(val arg1: KClass<*>, val arg2: KClass<out Any>)
@daqiAnnotation(String::class, Int::class) class MyClass
複製代碼
將其反編譯後,能夠看到轉換爲相應的Java類:post
@daqiAnnotation(
arg1 = String.class,
arg2 = int.class
)
public final class MyClass {
}
複製代碼
注意:註解參數不能有可空類型,由於 JVM 不支持將 null 做爲註解屬性的值存儲。gradle
和Java同樣,Kotlin的註解類也使用元註解進行註解。用於其餘註解的註解稱爲元註解,能夠理解爲最基本的標註。
Kotlin標準庫中定義了4個元註解,分別是:MustBeDocumented、Repeatable、Retention、Target。
@Target用於指定能夠應用該註解的元素類型(類、函數、屬性、表達式等)。
查看Target的源碼:
#Annotation.kt
@Target(AnnotationTarget.ANNOTATION_CLASS)
@MustBeDocumented
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
複製代碼
@Target註解中能夠同時接收一個或多個AnnotationTarget
枚舉值:
public enum class AnnotationTarget {
//做用於類(包括枚舉類)、接口、object對象和註解類
CLASS,
//僅做用於註解類
ANNOTATION_CLASS,
//做用於泛型類型參數(暫時不支持)(JDK8)
TYPE_PARAMETER,
//做用於屬性
PROPERTY,
//做用於字段(包括枚舉常量和支持字段)。
FIELD,
//做用於局部變量
LOCAL_VARIABLE,
//做用於函數或構造函數的參數
VALUE_PARAMETER,
//做用於構造函數(包括主構造函數和次構造函數)
CONSTRUCTOR,
//做用於方法(不包括構造函數)
FUNCTION,
//僅做用於屬性的getter函數
PROPERTY_GETTER,
//僅做用於屬性的setter函數
PROPERTY_SETTER,
//做用於類型(如方法內參數的類型)
TYPE,
//做用於表達式
EXPRESSION,
//做用於文件,可配合 file點目標 使用: (例如: @file:JvmName("daqiKotlin"))
FILE,
//做用於類型別名
@SinceKotlin("1.1")
TYPEALIAS
}
複製代碼
注意:Java代碼中沒法使用Target
爲AnnotationTarget.PROPERTY
的註解。若是想讓這樣的註解在Java中使用,能夠添加多一條AnnotationTarget.FIELD
的註解。
@Retention 聲明註解的保留策略。
查看Retention的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)
複製代碼
@Retention註解中能夠接收一個AnnotationRetention
枚舉值:
public enum class AnnotationRetention {
//表示註解僅保留在源代碼中,編譯器將丟棄該註解。
SOURCE,
//註解將由編譯器記錄在class文件中 但在運行時不須要由JVM保留。
BINARY,
//註解將由編譯器記錄在class文件中,並在運行時由JVM保留,所以能夠反射性地讀取它們。(默認行爲)
RUNTIME
}
複製代碼
注意:Java的元註解默認會在.class文件中保留註解,但不會讓它們在運行時被訪問到。大多數註解須要在運行時存在,以致於Kotlin將RUNTIME
做爲@Retention
註解的默認值。
容許在單個元素上屢次使用相同的該註解;
查看Repeatable的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable
複製代碼
注意:在"嘗試使用"@Repeatable
時發現,該註解必需要在Retention
元註解指定爲AnnotationRetention.SOURCE
時才能重複使用,但Java的@Repeatable
元註解並無該限制。(具體的Java @Repeatable
元註解的使用示例能夠看這篇文章)。由於@Repeatable
是Java 8引入的新的元註解,而兼容Java 6的Kotlin對此有點不兼容?
指定該註解是公有 API 的一部分,而且應該包含在生成的 API 文檔中顯示的類或方法的簽名中。
查看MustBeDocumented的源碼:
@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented
複製代碼
相對Java的5個元註解,Kotlin只提供了與其對應的4個元註解,Kotlin暫時不須要支持@Inherited
元註解。
@Inherited
註解代表註解類型能夠從超類繼承。具體意思是:存在一個帶@Inherited
元註解的註解類型,當用戶在某個類中查詢該註解類型而且沒有此類型的註解時,將嘗試從該類的超類以獲取註解類型。重複此過程,直到找到此類型的註解,或者到達類層次結構(對象)的頂部爲止。若是沒有超類具備此類型的註解,則查詢將指示相關類沒有此類註解。此註解僅適用於類聲明。
Kotlin爲了與Java具備良好的互通性,定義了一系列註解用於攜帶一些額外信息,以便編譯器作兼容轉換。
將Kotlin接口的默認方法生成Java 8的默認方法的字節碼
查看源碼:
@SinceKotlin("1.2")
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class JvmDefault
複製代碼
前面接口和類中提到,當在Kotlin中聲明一個帶默認方法的接口時,每每會將這些「默認方法」聲明爲抽象方法,同時也會在該接口中生成一個DefaultImpls
靜態內部類,並在其中定義同名的靜態方法來提供默認實現。
但這樣會存在一個問題,當對舊的Kotlin接口添加新的默認方法時,實現該接口的Java類須要從新實現新添的接口方法,不然會編譯不經過。同是默認方法,但與Java 8引入默認方法的初衷相違背。爲此,Kotlin提供了@JvmDefault
註解。對標有@JvmDefault
註解的默認方法,編譯器會將其編譯爲Java 8的默認接口。
#daqiKotlin.kt
public interface daqiInterface{
@JvmDefault//剛添加會報錯
fun daqiFunc() = println("帶@JvmDefault的默認方法")
fun daqiFunc2() = println("默認方法")
}
複製代碼
#java文件
public interface daqiInterface {
@JvmDefault
default void daqiFunc() {
String var1 = "帶@JvmDefault的默認方法";
System.out.println(var1);
}
void daqiFunc2();
public static final class DefaultImpls {
public static void daqiFunc2(daqiInterface $this) {
String var1 = "默認方法";
System.out.println(var1);
}
}
}
複製代碼
當你直接添加 @JvmDefault
時,編譯器會報錯。這時你須要在Gradle中配置如下參數:(具體Kotlin使用Gradle看官網)
tasks.withType<KotlinCompile> {
kotlinOptions {
freeCompilerArgs = ['-Xjvm-default = compatibility']
//freeCompilerArgs = ['-Xjvm-default = enable']
}
}
複製代碼
經過@JvmDefault
的註釋得知,配置時能夠選擇-Xjvm-default = enable
或-Xjvm-default = compatibility
。這兩個的區別是:
-Xjvm-default = enable
會從DefaultImpls
靜態內部類中刪除對應的方法。-Xjvm-default = compatibility
仍會在DefaultImpls
靜態內部類中保留對應的方法,提升兼容性。注意:只有JVM目標字節碼版本1.8(-jvm-target 1.8
)或更高版本才能生成默認方法。
指示Kotlin編譯器不爲此屬性生成getter / setter並將其修飾爲public。
查看源碼:
@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmField
複製代碼
Kotlin聲明的屬性都默認使用private
修飾,並提供setter
/ getter
訪問器對其進行訪問。而@JvmField
就是告訴編譯器不要爲該屬性自動建立setter
/ getter
訪問器,並將對其使用public
修飾。(用在伴生對象的屬性上,可生成public
修飾的static
屬性)
#daqiKotlin.kt
class Person{
@JvmField
val name:String = ""
}
複製代碼
反編譯後查看源碼中只聲明瞭一個public對象:
#java文件
public final class Person {
@JvmField
@NotNull
public final String name = "";
}
複製代碼
注意該註解只能用在有幕後字段的屬性上,對於沒有幕後字段的屬性(例如:擴展屬性、委託屬性等)不能使用。由於只有擁有幕後字段的屬性轉換成Java代碼時,纔有對應的Java變量。
Kotlin屬性擁有幕後字段須要知足如下條件之一:
指定生成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)
複製代碼
根據註解的聲明,屬性的訪問器getter / setter也可使用該註解,但屬性不能使用~。
在daqiKotlin.kt文件中聲明的全部函數和屬性(包括擴展函數)都被編譯爲名爲在DaqiKotlinKt的Java類的靜態方法。其中文件名首字母會被改成大寫,後置Kt。當須要修改該Kotlin文件生成的Java類名稱時,可使用@JvmName
名指定生成特定的文件名:
@file:JvmName("daqiKotlin")
package com.daqi.test
@JvmName("daqiStateFunc")
public fun daqiFunc(){
}
複製代碼
反編譯能夠看到生成的Java類名稱已經修改成daqiKotlin
,而非DaqiKotlinKt
,同時頂層函數daqiFunc
的方法名被修改成daqiStateFunc
:
public final class DaqiKotlinKt {
@JvmName(name = "daqiStateFunc")
public static final void daqiStateFunc() {
}
}
複製代碼
指示Kotlin編譯器生成一個多文件的類。該文件具備在此文件中聲明的頂級函數和屬性。
查看源碼:
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class JvmMultifileClass
複製代碼
當須要將多個Kotlin文件中的方法和屬性歸到一個Java類時,能夠在多個文件中聲明同樣的@JvmName
,並在其下面添加@JvmMultifileClass
註解。(多個文件中聲明同樣的@JvmName
,但不添加@JvmMultifileClass
註解會編譯不經過)
#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編譯器會將該兩個文件中的方法和屬性合併到@JvmName
註解生成的指定名稱的Java類中:
指示Kotlin編譯器爲此函數生成替換默認參數值的重載函數(從最後一個開始省略每一個參數)。
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmOverloads
複製代碼
Java並無參數默認值的概念,當你從Java中調用Kotlin的默認參數函數時,必須顯示地指定全部參數值。使用@JvmOverloads註解該方法,Kotlin編譯器會生成相應的Java重載函數,從最後一個參數開始省略每一個函數。
#daqiKotlin.kt
@JvmOverloads
fun daqi(name :String = "daqi",age :Int = 2019){
println("name = $name,age = $age ")
}
複製代碼
將對象聲明或伴生對象的方法或屬性的訪問器暴露成一個同名的Java靜態方法。
查看源碼:
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public actual annotation class JvmStatic
複製代碼
對於Kotlin的對象聲明和伴生對象,在Kotlin中能夠像靜態函數那樣調用類名.方法名進行調用。但在Java中,須要在這其中添加多一個Companion
或INSTANCE
,使調用很不天然。使用@JvmStatic
註解標記伴生對象或對象聲明中的方法和屬性,使其在Java中能夠像Kotlin同樣調用這些方法和屬性。
在Kotlin中定義一個伴生對象,並用標記@JvmStatic
註解:
class daqi{
companion object {
@JvmStatic
val name:String = ""
@JvmStatic
fun daqiFunc(){
}
}
}
複製代碼
反編譯能夠觀察到 伴生對象類 或 對象聲明類 中聲明瞭屬於它們本身的方法和屬性,但同時在對象聲明類自己或伴生對象類的外部類中也聲明瞭同樣的靜態的方法和屬性訪問器供外部直接訪問。
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() {
}
}
}
複製代碼
因此,若是對象聲明和伴生對象須要和Java層進行比較頻繁的交互時,建議仍是加上@JvmStatic
@JvmSuppressWildcards
指示編譯器爲泛型參數生成或省略通配符。(默認是省略)@JvmWildcard
指示編譯器爲爲泛型參數生成通配符。
查看源碼:
@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
複製代碼
指示Kotlin編譯器將帶該註釋的Java類視爲給定Kotlin接口的純實現。「Pure」在這裏表示類的每一個類型參數都成爲該接口的非平臺類型參數。
查看源碼:
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public annotation class PurelyImplements(val value: String)
複製代碼
Kotlin對來自Java的變量會看成平臺類型來處理,由開發者以爲其是可空仍是非空。但即使將其聲明爲非空,但其實他仍是能接收空值或者返回空值。
#java文件
class MyList<T> extends AbstractList<T> { ... }
複製代碼
#kotlin文件
MyList<Int>().add(null) // 編譯經過
複製代碼
但能夠藉助@PurelyImplements
註解,並攜帶對應的Kotlin接口。使其與Kotlin接口對應的類型參數不被看成平臺類型來處理。
#java文件
@PurelyImplements("kotlin.collections.MutableList")
class MyPureList<T> extends AbstractList<T> { ... }
複製代碼
MyPureList<Int>().add(null) // 編譯不經過
MyPureList<Int?>().add(null) // 編譯經過
複製代碼
等價於Java的throws關鍵字
查看源碼:
@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() {
}
複製代碼
等價於Java的strictfp關鍵字
查看源碼:
@Target(FUNCTION, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER, CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Strictfp
複製代碼
等價於Java的transient關鍵字
查看源碼:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Transient
複製代碼
等價於Java的synchronized關鍵字
查看源碼:
@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Synchronized
複製代碼
等價於Java的volatile關鍵字
查看源碼:
@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Volatile
複製代碼
許多狀況下, Kotlin代碼中的單個聲明會對應成多個 Java 聲明 ,並且它們每 個都能攜帶註解。例如, Kotlin 屬性就對應了 Java 宇段、 getter ,以 及一個潛在的 setter。這時須要使用點目標指定說明註解用在什麼地方。
點目標聲明被用來講明要註解的元素。使用點目標被放在@符號和註解名 稱之間,並用冒號和註解名稱隔開。
點目標的完整列表以下:
//註解的是get方法,而不是屬性
@get:daqiAnnotation
val daqi:String = ""
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class daqiAnnotation()
複製代碼