Kotlin知識概括(十三) —— 註解

前序

      註解是什麼?簡單說註解就是一種標註(標記、標識),沒有具體的功能邏輯代碼。經過註解開發人員能夠在不改變原有代碼和邏輯的狀況下在源代碼中嵌入補充信息。Kotlin註解的使用和Java徹底同樣,聲明註解類的語法略有不一樣。Java 註解與 Kotlin 100% 兼容。html

註解的定義

      註解能夠把額外的元數據關聯到一個聲明上,而後元數據能夠被反射機制或相關的源代碼工具訪問。java

聲明Kotlin的註解

      Kotlin的聲明註解的語法和常規類的聲明很是類似,但須要在class關鍵字以前加上annotation修飾符。但Kotlin編譯器禁止爲註解類指定類主體,由於註解類只是用來定義關聯到 聲明 和 表達式 的元數據的結構。android

#daqiKotlin.kt
annotation class daqiAnnotation
複製代碼

Java註解聲明:數組

#daqiJava.java
public @interface daqiAnnotation {
}
複製代碼

註解的構造函數

註解能夠有接受參數的構造函數。bash

其中註解的構造函數容許的參數類型有:jvm

  • 對應於 Java 原生類型的類型(Int、 Long等)
  • 字符串
  • 類(Foo::class)
  • 枚舉
  • 其餘註解
  • 上面已列類型的數組。

註解做爲註解構造函數的參數

當註解做爲另外一個註解的參數,則其名稱不用以 @ 字符爲前綴:函數

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

Kotlin的元註解

      和Java同樣,Kotlin的註解類也使用元註解進行註解。用於其餘註解的註解稱爲元註解,能夠理解爲最基本的標註。

      Kotlin標準庫中定義了4個元註解,分別是:MustBeDocumentedRepeatableRetentionTarget

@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代碼中沒法使用TargetAnnotationTarget.PROPERTY的註解。若是想讓這樣的註解在Java中使用,能夠添加多一條AnnotationTarget.FIELD的註解。

@Retention

@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

容許在單個元素上屢次使用相同的該註解;

查看Repeatable的源碼:

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class Repeatable
複製代碼

       注意:在"嘗試使用"@Repeatable時發現,該註解必需要在Retention元註解指定爲AnnotationRetention.SOURCE時才能重複使用,但Java的@Repeatable元註解並無該限制。(具體的Java @Repeatable元註解的使用示例能夠看這篇文章)。由於@Repeatable是Java 8引入的新的元註解,而兼容Java 6的Kotlin對此有點不兼容?

@MustBeDocumented

指定該註解是公有 API 的一部分,而且應該包含在生成的 API 文檔中顯示的類或方法的簽名中。

查看MustBeDocumented的源碼:

@Target(AnnotationTarget.ANNOTATION_CLASS)
public annotation class MustBeDocumented
複製代碼

消失的@Inherited元註解

      相對Java的5個元註解,Kotlin只提供了與其對應的4個元註解,Kotlin暫時不須要支持@Inherited元註解。

      @Inherited註解代表註解類型能夠從超類繼承。具體意思是:存在一個帶@Inherited元註解的註解類型,當用戶在某個類中查詢該註解類型而且沒有此類型的註解時,將嘗試從該類的超類以獲取註解類型。重複此過程,直到找到此類型的註解,或者到達類層次結構(對象)的頂部爲止。若是沒有超類具備此類型的註解,則查詢將指示相關類沒有此類註解。此註解僅適用於類聲明。

Kotlin預約義的註解

      Kotlin爲了與Java具備良好的互通性,定義了一系列註解用於攜帶一些額外信息,以便編譯器作兼容轉換。

@JvmDefault

將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)或更高版本才能生成默認方法。

@JvmField

指示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屬性擁有幕後字段須要知足如下條件之一:

  • 使用默認 getter / setter 的屬性,必定有幕後字段。對於 var 屬性來講,只要 getter / setter 中有一個使用默認實現,就會生成幕後字段。
  • 在自定義 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)
複製代碼

      根據註解的聲明,屬性的訪問器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() {
   }
}
複製代碼

@JvmMultifileClass

指示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類中:

@JvmOverloads

指示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 ")
}
複製代碼

@JvmStatic

將對象聲明或伴生對象的方法或屬性的訪問器暴露成一個同名的Java靜態方法。

查看源碼:

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
public actual annotation class JvmStatic
複製代碼

      對於Kotlin的對象聲明和伴生對象,在Kotlin中能夠像靜態函數那樣調用類名.方法名進行調用。但在Java中,須要在這其中添加多一個CompanionINSTANCE,使調用很不天然。使用@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

@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
複製代碼

@PurelyImplements

指示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) // 編譯經過
複製代碼

@Throws

等價於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() {
    
}
複製代碼

@Strictfp

等價於Java的strictfp關鍵字

查看源碼:

@Target(FUNCTION, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER, CLASS)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Strictfp
複製代碼

@Transient

等價於Java的transient關鍵字

查看源碼:

@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Transient
複製代碼

@Synchronized

等價於Java的synchronized關鍵字

查看源碼:

@Target(FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Synchronized
複製代碼

@Volatile

等價於Java的volatile關鍵字

查看源碼:

@Target(FIELD)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
public actual annotation class Volatile
複製代碼

點目標聲明

      許多狀況下, Kotlin代碼中的單個聲明會對應成多個 Java 聲明 ,並且它們每 個都能攜帶註解。例如, Kotlin 屬性就對應了 Java 宇段、 getter ,以 及一個潛在的 setter。這時須要使用點目標指定說明註解用在什麼地方。

點目標聲明被用來講明要註解的元素。使用點目標被放在@符號和註解名 稱之間,並用冒號和註解名稱隔開。

點目標的完整列表以下:

  • property————Java 的註解不能應用這種使用點目標
  • field————爲屬性生成的字段
  • get ————屬性的 getter
  • set ————屬性的 setter
  • receiver ————擴展函數或者擴展屬性的接收者參數。
  • param————構造方法的參數。
  • setparam————屬性 setter 的參數
  • delegate ————爲委託屬性存儲委託實例的字段
  • file ———— 包含在文件中聲明的頂層函數和屬性的類。
//註解的是get方法,而不是屬性
@get:daqiAnnotation
val daqi:String = ""

@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class daqiAnnotation()
複製代碼

參考資料:

android Kotlin系列:

Kotlin知識概括(一) —— 基礎語法

Kotlin知識概括(二) —— 讓函數更好調用

Kotlin知識概括(三) —— 頂層成員與擴展

Kotlin知識概括(四) —— 接口和類

Kotlin知識概括(五) —— Lambda

Kotlin知識概括(六) —— 類型系統

Kotlin知識概括(七) —— 集合

Kotlin知識概括(八) —— 序列

Kotlin知識概括(九) —— 約定

Kotlin知識概括(十) —— 委託

Kotlin知識概括(十一) —— 高階函數

Kotlin知識概括(十二) —— 泛型

Kotlin知識概括(十三) —— 註解

Kotlin知識概括(十四) —— 反射

相關文章
相關標籤/搜索