教你如何完全解析Kotlin中的註解

極客熊貓發表於2019-04-24

簡述: 從這篇文章將繼續開始探索Kotlin中的一些高階的內容,之前有著重探討了Kotlin的泛型以及泛型型變等內容。現在我們一起來看下Kotlin中的註解。Kotlin中的註解是100%與Java註解相容的,有很多相同的地方,但是也有一些不同的地方。一起來瞅瞅吧~

一、註解的本質

註解實際上就是一種程式碼標籤,它作用的物件是程式碼。它可以給特定的註解程式碼標註一些額外的資訊。然而這些資訊可以選擇不同保留時期,比如原始碼期、編譯期、執行期。然後在不同時期,可以通過某種方式獲取標籤的資訊來處理實際的程式碼邏輯,這種方式常常就是我們所說的反射

二、註解的定義

在Kotlin中註解核心概念和Java一樣,註解就是為了給程式碼提供後設資料。並且註解是不直接影響程式碼的執行。一個註解允許你把額外的後設資料關聯到一個宣告上,然後後設資料就可以被某種方式(比如執行時反射方式以及一些原始碼工具)訪問

三、註解的宣告(標籤的宣告)

在Kotlin中的宣告註解的方式和Java稍微不一樣,在Java中主要是通過 @interface關鍵字來宣告,而在Kotlin中只需要通過 annotation class 來宣告, 需要注意的是在Kotlin中編譯器禁止為註解類指定類主體,因為在Kotlin中註解只是用來定義關聯的宣告和表示式的後設資料的結構。

  • 1、Kotlin註解宣告
package com.mikyou.annotation
//和一般的宣告很類似,只是在class前面加上了annotation修飾符
annotation class TestAnnotation(val value: String)
複製程式碼
  • 2、Java註解宣告
package com.mikyou.annotation;
//java中的註解通過@interface關鍵字進行定義,它和介面宣告類似,只不過在前面多加@
public @interface TestAnnotation {
    String value();
}
複製程式碼

四、註解的應用

  • 1、在上一步我們知道了如何宣告和定義標籤了,那麼接下來就是用這個標籤,如何把我們定義好的標籤貼到指定的程式碼上。在Kotlin中使用註解和Java一樣。要應用一個註解都是 @註解類名
@Target(AnnotationTarget.FUNCTION)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class TestAnnotation(val value: Int)//和一般的宣告很類似,只是在class前面加上了annotation修飾符

class Test {
    @TestAnnotation(value = 1000)
    fun test() {//給test函式貼上TestAnnotation標籤(新增TestAnnotation註解)
        //...
    }
}
複製程式碼
  • 2、在很多常見的Java或Kotlin框架中大量使用了註解,比如我們最常見的JUnit單元測試框架
class ExampleUnitTest {
    @Test //@Test註解就是為了告訴JUnit框架,這是一個測試方法,當做測試呼叫。
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}
複製程式碼
  • 3、在Kotlin中註解類中還可以擁有註解類作為引數,不妨來下Kotlin中對 @Deprecated這個註解原始碼定義,以及它的使用。@Deprecated註解在原來的Java基礎增強了一個ReplaceWith功能. 可以直接在使用了老的API時,編譯器可以根據ReplaceWith中的新API,自動替換成新的API。這一點在Java中是做不到的,你只能點選進入這個API檢視原始碼來正確使用新的API。
//@Deprecated註解比Java多了ReplaceWith功能, 這樣當你在呼叫remove方法,編譯器會報錯。使用程式碼提示會自動IntelliJ IDEA不僅會提示使用哪個函式提示替代它,而且會快速自動修正。
@Deprecated("Use removeAt(index) instead.", ReplaceWith("removeAt(index)"), level = DeprecationLevel.ERROR)//定義的級別是ERROR級別的,這樣當你在呼叫remove方法,編譯器會報錯。
@kotlin.internal.InlineOnly
public inline fun <T> MutableList<T>.remove(index: Int): T = removeAt(index)
複製程式碼

@Deprecated註解的remove函式使用

//Deprecated註解的使用
fun main(args: Array<String>) {
    val list = mutableListOf("a", "b", "c", "d", "e")
    list.remove(3)//這裡會報錯, 通過remove函式註解定義,這個remove函式在定義的level是ERROR級別的,所以編譯器直接拋錯
}
複製程式碼

教你如何完全解析Kotlin中的註解

教你如何完全解析Kotlin中的註解

最後來看下@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
)
@Target()
@Retention(BINARY)
@MustBeDocumented
public annotation class ReplaceWith(val expression: String, vararg val imports: String)
複製程式碼

注意: 註解類中只能擁有如下型別的引數: 基本資料型別、字串、列舉、類引用型別、其他的註解類(例如Deprecated註解類中的ReplaceWith註解類)

五、Kotlin中的元註解

和Java一樣在Kotlin中,一個Kotlin註解類自己本身也可以被註解,可以給註解類加註解。我們把這種註解稱為元註解,可以把它理解為一種基本的註解,可以把它理解為一種特殊的標籤,用於標註標籤的標籤。

Kotlin中的元註解類定義於kotlin.annotation包中,主要有: @Target@Retention@Repeatable@MustBeDocumented 4種元註解相比Java中5種元註解: @Target@Retention@Repeatable@Documented@Inherited少了 @Inherited元註解。

@Target元註解

  • 1、介紹

Target顧名思義就是目標物件,也就是這個標籤作用於哪些程式碼中目標物件,可以同時指定多個作用的目標物件。

  • 2、原始碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//可以給標籤自己貼標籤
@MustBeDocumented
//註解類構造器引數是個vararg不定引數修飾符,所以可以同時指定多個作用的目標物件
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
複製程式碼
  • 3、@Target元註解作用的目標物件

在@Target註解中可以同時指定一個或多個目標物件,那麼到底有哪些目標物件呢?這就引出另外一個AnnotationTarget列舉類

public enum class AnnotationTarget {
    CLASS, //表示作用物件有類、介面、object物件表示式、註解類
    ANNOTATION_CLASS,//表示作用物件只有註解類
    TYPE_PARAMETER,//表示作用物件是泛型型別引數(暫時還不支援)
    PROPERTY,//表示作用物件是屬性
    FIELD,//表示作用物件是欄位,包括屬性的幕後欄位
    LOCAL_VARIABLE,//表示作用物件是區域性變數
    VALUE_PARAMETER,//表示作用物件是函式或建構函式的引數
    CONSTRUCTOR,//表示作用物件是建構函式,主建構函式或次建構函式
    FUNCTION,//表示作用物件是函式,不包括建構函式
    PROPERTY_GETTER,//表示作用物件是屬性的getter函式
    PROPERTY_SETTER,//表示作用物件是屬性的setter函式
    TYPE,//表示作用物件是一個型別,比如類、介面、列舉
    EXPRESSION,//表示作用物件是一個表示式
    FILE,//表示作用物件是一個File
    @SinceKotlin("1.1")
    TYPEALIAS//表示作用物件是一個型別別名
}
複製程式碼

@Retention元註解

  • 1、介紹

Retention對應的英文意思是保留期,當它應用於一個註解上表示該註解保留存活時間,不管是Java還是Kotlin一般都有三種時期: 原始碼時期(SOURCE)編譯時期(BINARY)執行時期(RUNTIME)

  • 2、原始碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標物件是註解類
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)//接收一個引數,該引數有個預設值,預設是保留在執行時期
複製程式碼
  • 3、@Retention元註解的取值

@Retention元註解取值主要來源於AnnotationRetention列舉類

public enum class AnnotationRetention {
    SOURCE,//原始碼時期(SOURCE): 註解不會儲存在輸出class位元組碼中
    BINARY,//編譯時期(BINARY): 註解會儲存出class位元組碼中,但是對反射不可見
    RUNTIME//執行時期(RUNTIME): 註解會儲存出class位元組碼中,也會對反射可見, 預設是RUNTIME
}
複製程式碼

@MustBeDocumented元註解

  • 1、介紹

該註解比較簡單主要是為了標註一個註解類作為公共API的一部分,並且可以保證該註解在生成的API文件中存在。

  • 2、原始碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標物件只能是註解類
public annotation class MustBeDocumented
複製程式碼

@Repeatable元註解

  • 1、介紹

這個註解決定標註的註解在一個註解在一個程式碼元素上可以應用兩次或兩次以上。

  • 2、原始碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標物件只能是註解類
public annotation class Repeatable
複製程式碼

為啥Kotlin去掉了Java中的@Inherited元註解

  • 1、Java中的@Inherited元註解介紹

Inheried顧名思義就是繼承的意思,但是這裡需要注意並不是表示註解類可以繼承,而是如果一個父類被貼上@Inherited元註解標籤,那麼它的子類沒有任何註解標籤的話,這個子類就會繼承來自父類的註解。類似下面的例子:

@Inherited
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

@TestAnnotation
class Animal {
    //...
}

class Cat extends Animal{//也會擁有來自父類Animal的@TestAnnotation註解
    //...
}
複製程式碼
  • 2、Kotlin為啥不需要@Inherited元註解

關於這個問題實際上在Kotlin官網的discuss中就有人提出了這個問題,具體感興趣的可以去看看:Inherited annotations and other reflections enchancements. 這裡大概說下原因,我們都知道在Java中,無法找到子類方法是否重寫了父類的方法。因此不能繼承父類方法的註解。然而Kotlin目前不需要支援這個@Inherited元註解,因為Kotlin可以做到,如果反射提供了override標記而且很容易做到。

六、註解的使用場景

  • 1、提供資訊給編譯器: 編譯器可以利用註解來處理一些,比如一些警告資訊,錯誤等
  • 2、編譯階段時處理: 利用註解資訊來生成一些程式碼,在Kotlin生成程式碼非常常見,一些內建的註解為了與Java API的互操作性,往往藉助註解在編譯階段生成一些額外的程式碼。
  • 3、執行時處理: 某些註解可以在程式執行時,通過反射機制獲取註解資訊來處理一些程式邏輯。

七、Kotlin中的預置註解

在Kotlin中最大的一個特點就是可以和Java做到極高的互操作性,我們知道Kotlin的語法和Java語法還是有很大的不同,要想做到與Java做到很大相容性可能需要攜帶一些額外資訊,供編譯器或者執行時做類似相容轉換。其中註解就起到了很大的作用,在Kotlin內建很多個註解為了解決Java中的呼叫Kotlin API的一些呼叫習慣和控制API的呼叫。它們就是Kotlin中的@Jvm系列的註解,我們們一一來看下它們都有哪些。

@JvmDefault

@JvmDefault註解是在Kotlin 1.2.40版本加入的,並且在之後的Kotlin 1.2.50版本增強一些實驗性特性。

  • 1、作用

我們都知道在Kotlin中的介面中可以增加非抽象成員,那麼該註解就是為非抽象的介面成員生成預設的方法。

使用-Xjvm-default = enable,會為每個@JvmDefault註解標註的方法生成介面中的預設方法。在此模式下,使用@JvmDefault註解現有方法可能會破壞二進位制相容性,因為它將有效地從DefaultImpls類中刪除該方法。

使用-Xjvm-default = compatibility,除了預設介面方法之外,還會生成相容性訪問器。在DefaultImpls類中,它通過合成訪問器呼叫預設介面方法。在此模式下,使用@JvmDefault註解現有方法是二進位制相容的,但在位元組碼中會產生更多方法。從介面成員中移除此註解會使在兩種模式中的二進位制不相容性發生變化。

  • 2、原始碼定義
@SinceKotlin("1.2")//從Kotlin的1.2版本第一次出現該註解
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)//目標物件是函式和屬性
annotation class JvmDefault
複製程式碼
  • 3、使用註解前後反編譯java程式碼對比

未使用@JvmDefault註解

interface ITeaching {
    fun speak() = println("open the book")
}

class ChineseTeacher : ITeaching

fun main(args: Array<String>) {
    ChineseTeacher().speak()
}
複製程式碼

反編譯成Java程式碼

public interface ITeaching {
   void speak();
   public static final class DefaultImpls {//可以看到在介面為speak函式生成一個DefaultImpls靜態內部類
      public static void speak(ITeaching $this) {
         String var1 = "open the book";
         System.out.println(var1);
      }
   }
}

public final class ChineseTeacher implements ITeaching {
   public void speak() {
      ITeaching.DefaultImpls.speak(this);//注意:這裡卻是直接呼叫ITeaching中靜態內部類DefaultImpls的speak方法。
   }
}

public final class JvmDefaultTestKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      (new ChineseTeacher()).speak();//這裡會呼叫ChineseTeacher中的speak方法
   }
}
複製程式碼

使用@JvmDefault註解

interface ITeaching {
    @JvmDefault//注意: 可能一開始使用該註解會報錯,需要在gradle中配置jvm引數:-jvm-target=1.8 -Xjvm-default=enable
    fun speak() = println("open the book")
}

class ChineseTeacher : ITeaching

fun main(args: Array<String>) {
    ChineseTeacher().speak()
}
複製程式碼

反編譯成Java程式碼

public interface ITeaching {
   @JvmDefault
   default void speak() {//新增註解後外層的靜態內部類被消除
      String var1 = "open the book";
      System.out.println(var1);
   }
}

public final class ChineseTeacher implements ITeaching {//內部並沒有類似上面的speak方法呼叫的橋接委託
}

public final class JvmDefaultTestKt {
   public static final void main(@NotNull String[] args) {
      Intrinsics.checkParameterIsNotNull(args, "args");
      (new ChineseTeacher()).speak();
   }
}
複製程式碼

總而言之,在沒有新增 @JvmDefault註解,Kotlin會自動生成一個叫做 DefaultImpl靜態內部類,用於儲存靜態方法的預設實現,並使用自身接收器型別來模擬屬於物件的方法。然後,對於擴充套件該介面的每種型別,如果型別沒有實現方法本身,則在編譯時,Kotlin將通過呼叫將方法連線到預設實現。

這樣一來確實帶來一個很大好處就是在JDK1.8之前的版本JVM上提供了在介面上也能定義具體的實現方法功能。但是這樣也存在一些問題:

第一問題: 比如它和現在的Java的處理方式不相容,這樣會導致互操作性極度下降。我們甚至可以在Java中直接去呼叫自動生成的DefaultImpls,類似這樣的呼叫ITeaching.DefaultImpls.speak(new ChineseTeacher());,這樣內部的細節居然也能暴露給外部,這樣更會呼叫者一臉懵逼。

第二問題: Java 8中存在預設方法的主要原因之一是能夠向介面新增方法而無需侵入每個子類。 然而Kotlin實現不支援這個原因是必須在每個具體型別上生成預設呼叫。 向介面新增新方法導致必須重新編譯每個實現者。

基於上述問題,Kotlin推出了 @JvmDefault註解

@JvmField

  • 1、作用

可以應用於一個欄位,把這個屬性暴露成一個沒有訪問器的公有Java欄位;以及Companion Object物件中。

  • 2、原始碼定義
@Target(AnnotationTarget.FIELD)//作用物件是欄位,包括屬性的幕後欄位
@Retention(AnnotationRetention.BINARY)//註解保留期是原始碼階段
@MustBeDocumented
public actual annotation class JvmField
複製程式碼
  • 3、註解使用

使用場景一:

我們知道在Kotlin中預設情況下,Kotlin類不會公開欄位而是會公開屬性.Kotlin會為屬性的提供幕後欄位,這些屬性將會以欄位形式儲存它的值。一起來看個例子

//Person類中定義一個age屬性,age屬性預設是public公開的,但是反編譯成Java程式碼,你就會看到它的幕後欄位了。
class Person {
    var age = 18
        set(value) {
            if (value > 0) field = value
        }
}
複製程式碼

反編譯成Java程式碼

public final class Person {
   private int age = 18;//這個就是Person類中的幕後欄位,可以看到age欄位是private私有的。

  //外部訪問通過setter和getter訪問器來操作。由於Kotlin自動生成setter、getter訪問器,所以外部可以直接類似公開屬性操作,
  //實際上內部還是通過setter、getter訪問器來實現
   public final int getAge() {
      return this.age;
   }

   public final void setAge(int value) {
      if (value > 0) {
         this.age = value;
      }
   }
}
複製程式碼

但是如果在Kotlin需要生成一個公開的欄位怎麼實現呢?那就要藉助@JvmField註解了,它會自動將該欄位的setter、getter訪問器消除掉,並且把這個欄位修改為public

class Person {
    @JvmField
    var age = 18
}
複製程式碼

反編譯成的Java程式碼

public final class Person {
   @JvmField
   public int age = 18;//消除了setter、getter訪問器,並且age欄位為public公開
}
複製程式碼

使用場景二:

@JvmField另一個經常使用的場景就是用於Companion Object伴生物件中。

未使用@JvmField註解

class Person {
    companion object {
        val MAX_AGE = 120
    }
}
複製程式碼

反編譯成Java程式碼

public final class Person {
   private static final int MAX_AGE = 120;//注意: 這裡預設是private私有的MAX_AGE,所以在Java中呼叫無法直接通過Person類名.變數名訪問
   public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);
   public static final class Companion {
   //在Java中呼叫無法直接通過Person類名.變數名訪問, 
   //而是通過靜態內部類Companion的getMAX_AGE間接訪問,類似這樣Person.Companion.getMAX_AGE();
      public final int getMAX_AGE() {
         return Person.MAX_AGE;
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
複製程式碼

但是如果使用該註解就能直接通過Person類名.變數名訪問

class Person {
    companion object {
        @JvmField
        val MAX_AGE = 120
    }
}
//在Java中呼叫
public static void main(String[] args) {
    System.out.println(Person.MAX_AGE);//可以直接呼叫,因為它已經變成了public了
}
複製程式碼

反編譯成Java程式碼

public final class Person {
   @JvmField
   public static final int MAX_AGE = 120;//公有的MAX_AGE的,外部可以直接呼叫
   public static final Person.Companion Companion = new Person.Companion((DefaultConstructorMarker)null);
   public static final class Companion {
      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
複製程式碼

@JvmMultifileClass

  • 1、作用

該註解主要是為了生成多檔案的類

  • 2、原始碼定義
@Target(AnnotationTarget.FILE)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmMultifileClass()
複製程式碼
  • 3、註解使用

在Kotlin分別定義兩個頂層函式在兩個不同檔案中,可通過該註解將多個檔案中的類方法合併到一個類中。

//存在於IOUtilA檔案中
@file:JvmName("IOUtils")
@file:JvmMultifileClass

package com.mikyou.annotation

import java.io.IOException
import java.io.Reader


fun closeReaderQuietly(input: Reader?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}

//存在於IOUtilB檔案中
@file:JvmName("IOUtils")
@file:JvmMultifileClass

package com.mikyou.annotation

import java.io.IOException
import java.io.InputStream


fun closeStreamQuietly(input: InputStream?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}
//在Java中使用
public class Test {
    public static void main(String[] args) {
        //即使存在於不同檔案中,但是對於外部Java呼叫仍然是同一個類IOUtils
        IOUtils.closeReaderQuietly(null);
        IOUtils.closeStreamQuietly(null);
    }
}
複製程式碼

@JvmName

  • 1、作用

將改變由Kotlin預設生成的Java方法、欄位或類名

  • 2、原始碼定義
//作用的目標有: 函式、屬性getter方法、屬性setter方法、檔案
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmName(actual val name: String)//有個name引數,將生成傳入指定name的名稱
複製程式碼
  • 3、註解使用
class Student {
    @get:JvmName(name = "getStudentName")//修改屬性的getter函式名稱
    @set:JvmName(name = "setStudentName")//修改屬性的setter函式名稱
    var name: String = "Tim"

    @JvmName("getStudentScore")//修改函式名稱
    fun getScore(): Double {
        return 110.5
    }
}
//修改生成的類名,預設Kotlin會生成以檔名+Kt字尾組合而成的類名
@file:JvmName("IOUtils")//注意:該註解一定要在第一行,package頂部
package com.mikyou.annotation

import java.io.IOException
import java.io.Reader


fun closeReaderQuietly(input: Reader?) {
    try {
        input?.close()
    } catch (ioe: IOException) {
        // ignore
    }
}
複製程式碼

反編譯後的Java程式碼

public final class Student {
   @NotNull
   private String name = "Tim";

   @JvmName(name = "getStudentName")
   @NotNull
   //已經修改成傳入getStudentName
   public final String getStudentName() {
      return this.name;
   }

   @JvmName(name = "setStudentName")
   //已經修改成傳入setStudentName
   public final void setStudentName(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.name = var1;
   }

   @JvmName(name = "getStudentScore")
   //已經修改成傳入getStudentScore
   public final double getStudentScore() {
      return 110.5D;
   }
}
複製程式碼

@JvmOverloads

  • 1、作用

指導Kotlin編譯器為帶預設引數值的函式(包括建構函式)生成多個過載函式。

  • 2、原始碼定義
//作用物件是函式和建構函式
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmOverloads()
複製程式碼
  • 3、註解使用

該註解使用最多就是用於帶預設值函式的過載,在Android中我們在自定義View的時候一般會過載多個構造器,需要加入該註解,如果不加預設只定義一個構造器,那麼當你直接在XML使用這個自定義View的時候會丟擲異常。

class ScrollerView @JvmOverloads constructor(
    context: Context,
    attr: AttributeSet? = null,
    defStyle: Int = 0
) : View(context, attr, defStyle) {
    //...
}
複製程式碼

反編譯後的Java程式碼

public final class ScrollerView extends View {
   @JvmOverloads
   public ScrollerView(@NotNull Context context, @Nullable AttributeSet attr, int defStyle) {
      Intrinsics.checkParameterIsNotNull(context, "context");
      super(context, attr, defStyle);
   }

   // $FF: synthetic method
   @JvmOverloads
   public ScrollerView(Context var1, AttributeSet var2, int var3, int var4, DefaultConstructorMarker var5) {
      if ((var4 & 2) != 0) {
         var2 = (AttributeSet)null;
      }
      if ((var4 & 4) != 0) {
         var3 = 0;
      }
      this(var1, var2, var3);
   }

   @JvmOverloads
   public ScrollerView(@NotNull Context context, @Nullable AttributeSet attr) {
      this(context, attr, 0, 4, (DefaultConstructorMarker)null);
   }

   @JvmOverloads
   public ScrollerView(@NotNull Context context) {
      this(context, (AttributeSet)null, 0, 6, (DefaultConstructorMarker)null);
   }
   //...
}
複製程式碼

@JvmPackageName

  • 1、作用

更改從使用該註解標註的檔案生成的.class檔案的JVM包的完全限定名稱。 這不會影響Kotlin客戶端在此檔案中檢視宣告的方式,但Java客戶端和其他JVM語言客戶端將看到類檔案,就好像它是在指定的包中宣告的那樣。 如果使用此批註對檔案進行批註,則它只能包含函式,屬性和型別宣告,但不能包含。

  • 2、原始碼定義
@Target(AnnotationTarget.FILE)//作用於檔案
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@SinceKotlin("1.2")//Kotlin1.2版本加入
internal annotation class JvmPackageName(val name: String)
複製程式碼
  • 3、註解使用
//以Collection原始碼為例
@file:kotlin.jvm.JvmPackageName("kotlin.collections.jdk8")

package kotlin.collections
複製程式碼

可以看到該類會編譯生成到kotlin.collections.jdk8包名下

教你如何完全解析Kotlin中的註解

@JvmStatic

  • 1、作用

能被用在物件宣告或者Companion object伴生物件的方法上,把它們暴露成一個Java的靜態方法

  • 2、原始碼定義
//作用於函式、屬性、屬性的setter和getter
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmStatic()
複製程式碼
  • 3、註解使用

@JvmStatic這個註解一般經常用於伴生物件的方法上,供給Java程式碼呼叫

class Data {
    companion object {
        fun getDefaultDataName(): String {
            return "default"
        }
    }
}
//在java中呼叫,只能是Data.Companion.getDefaultDataName()呼叫
public class Test {
    public static void main(String[] args) {
        System.out.println(Data.Companion.getDefaultDataName());
    }
}
複製程式碼

反編譯後Java程式碼

public final class Data {
   public static final Data.Companion Companion = new Data.Companion((DefaultConstructorMarker)null);
   public static final class Companion {
      @NotNull
      public final String getDefaultDataName() {
         return "default";
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
複製程式碼

使用@JvmStatic註解後

class Data {
    companion object {
        @JvmStatic
        fun getDefaultDataName(): String {
            return "default"
        }
    }
}
//在java中呼叫,可以直接這樣Data.getDefaultDataName()呼叫
public class Test {
    public static void main(String[] args) {
        System.out.println(Data.getDefaultDataName());
    }
}
複製程式碼

反編譯後的Java程式碼

public final class Data {
   public static final Data.Companion Companion = new Data.Companion((DefaultConstructorMarker)null);

   @JvmStatic
   @NotNull
   //注意它會在Data類內部自動生成一個getDefaultDataName,然後內部還是通過Companion.getDefaultDataName()去呼叫。
   public static final String getDefaultDataName() {
      return Companion.getDefaultDataName();
   }

   public static final class Companion {
      @JvmStatic
      @NotNull
      public final String getDefaultDataName() {
         return "default";
      }

      private Companion() {
      }

      // $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}
複製程式碼

@JvmSuppressWildcards和@JvmWildcard

  • 1、作用

用於指示編譯器生成或省略型別引數的萬用字元,JvmSuppressWildcards用於引數的泛型是否生成或省略萬用字元,而JvmWildcard用於返回值的型別是否生成或省略萬用字元

  • 2、原始碼定義
//作用於類、函式、屬性、型別
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE)
@MustBeDocumented
@OptionalExpectation
//指定suppress為true表示不生成,false為生成萬用字元,預設是true不生成
public expect annotation class JvmSuppressWildcards(val suppress: Boolean = true)

@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
@MustBeDocumented
public actual annotation class JvmWildcard
複製程式碼
  • 3、註解使用
interface ICovert {
    fun covertData(datas: List<@JvmSuppressWildcards(suppress = false) String>)//@JvmSuppressWildcardsd用於引數型別

    fun getData(): List<@JvmWildcard String>//@JvmWildcard用於返回值型別
}
複製程式碼
class CovertImpl implements ICovert {
    @Override
    public void covertData(List<? extends String> datas) {//引數型別生成萬用字元

    }
    @Override
    public List<? extends String> getData() {//返回值型別生成萬用字元
        return null;
    }
}
複製程式碼

@JvmSynthetic

  • 1、作用

它在生成的類檔案中將適當的元素標記為合成,並且編譯器標記為合成的任何元素都將無法從Java語言中訪問。

  • 2、什麼是合成屬性(Synthetic屬性)?

JVM位元組碼標識的ACC_SYNTHETIC屬性用於標識該元素實際上不存在於原始原始碼中,而是由編譯器生成。

  • 3、合成屬效能做什麼?

它一般用於支援程式碼生成,允許編譯器生成不應向其他開發人員公開但需要支援實際公開介面所需的欄位和方法。我們可以將其視為超越private或protected級別。

  • 4、原始碼定義
//作用於函式、屬性的setter,getter以及欄位
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FIELD)
@OptionalExpectation
public expect annotation class JvmSynthetic()
複製程式碼
  • 5、註解使用
class Synthetic {
    @JvmSynthetic
    val name: String = "Tim"
    var age: Int
        @JvmSynthetic
        set(value) {
        }
        @JvmSynthetic
        get() {
            return 18
        }
}
複製程式碼

反編譯後的Java程式碼

public final class Synthetic {
   // $FF: synthetic field
   @NotNull
   private final String name = "Tim";

   @NotNull
   public final String getName() {
      return this.name;
   }

   // $FF: synthetic method//我們經常看到這些註釋,就是通過@Synthetic註解生成的
   public final int getAge() {
      return 18;
   }

   // $FF: synthetic method
   public final void setAge(int value) {
   }
}
複製程式碼

通過反編譯程式碼可能看不到什麼,我們直接可以通過javap -v xxx.class查閱生成的位元組碼檔案描述

  public final int getAge();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//新增ACC_SYNTHETIC標識
    Code:
      stack=1, locals=1, args_size=1
         0: bipush        18
         2: ireturn
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       3     0  this   Lcom/mikyou/annotation/Synthetic;
      LineNumberTable:
        line 12: 0

  public final void setAge(int);
    descriptor: (I)V
    flags: ACC_PUBLIC, ACC_FINAL, ACC_SYNTHETIC//新增ACC_SYNTHETIC標識
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lcom/mikyou/annotation/Synthetic;
            0       1     1 value   I
      LineNumberTable:
        line 9: 0

複製程式碼

@Throws

  • 1、作用

用於Kotlin中的函式,屬性的setter或getter函式,構造器函式丟擲異常

  • 2、原始碼定義
//作用於函式、屬性的getter、setter函式、構造器函式
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
public annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)//這裡是異常類KClass不定引數,可以同時指定一個或多個異常
複製程式碼
  • 3、註解使用
@Throws(IOException::class)
fun closeQuietly(output: Writer?) {
    output?.close()
}
複製程式碼

@Transient

該註解充當了Java中的transient關鍵字

@Strictfp

該註解充當了Java中的strictfp關鍵字

@Synchronized

該註解充當了Java中的synchronized關鍵字

@Volatile

該註解充當了Java中的volatile關鍵字

教你如何完全解析Kotlin中的註解

歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~

Kotlin系列文章,歡迎檢視:

Kotlin邂逅設計模式系列:

資料結構與演算法系列:

翻譯系列:

原創系列:

Effective Kotlin翻譯系列

實戰系列:

相關文章