教你如何徹底解析Kotlin中的註解

簡述: 從這篇文章將繼續開始探索Kotlin中的一些高級的內容,以前有着重探討了Kotlin的泛型以及泛型型變等內容。如今咱們一塊兒來看下Kotlin中的註解。Kotlin中的註解是100%與Java註解兼容的,有不少相同的地方,可是也有一些不一樣的地方。一塊兒來瞅瞅吧~java

1、註解的本質

註解實際上就是一種代碼標籤,它做用的對象是代碼。它能夠給特定的註解代碼標註一些額外的信息。然而這些信息能夠選擇不一樣保留時期,好比源碼期、編譯期、運行期。而後在不一樣時期,能夠經過某種方式獲取標籤的信息來處理實際的代碼邏輯,這種方式經常就是咱們所說的反射算法

2、註解的定義

在Kotlin中註解核心概念和Java同樣,註解就是爲了給代碼提供元數據。而且註解是不直接影響代碼的執行。一個註解容許你把額外的元數據關聯到一個聲明上,而後元數據就能夠被某種方式(好比運行時反射方式以及一些源代碼工具)訪問express

3、註解的聲明(標籤的聲明)

在Kotlin中的聲明註解的方式和Java稍微不同,在Java中主要是經過 @interface關鍵字來聲明,而在Kotlin中只須要經過 annotation class 來聲明, 須要注意的是在Kotlin中編譯器禁止爲註解類指定類主體,由於在Kotlin中註解只是用來定義關聯的聲明和表達式的元數據的結構。設計模式

  • 一、Kotlin註解聲明
package com.mikyou.annotation
//和通常的聲明很相似,只是在class前面加上了annotation修飾符
annotation class TestAnnotation(val value: String)
複製代碼
  • 二、Java註解聲明
package com.mikyou.annotation;
//java中的註解經過@interface關鍵字進行定義,它和接口聲明相似,只不過在前面多加@
public @interface TestAnnotation {
    String value();
}
複製代碼

4、註解的應用

  • 一、在上一步咱們知道了如何聲明和定義標籤了,那麼接下來就是用這個標籤,如何把咱們定義好的標籤貼到指定的代碼上。在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註解)
        //...
    }
}
複製代碼
  • 二、在不少常見的Java或Kotlin框架中大量使用了註解,好比咱們最多見的JUnit單元測試框架
class ExampleUnitTest {
    @Test //@Test註解就是爲了告訴JUnit框架,這是一個測試方法,當作測試調用。
    fun addition_isCorrect() {
        assertEquals(4, 2 + 2)
    }
}
複製代碼
  • 三、在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級別的,因此編譯器直接拋錯
}
複製代碼

最後來看下@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註解類)app

5、Kotlin中的元註解

和Java同樣在Kotlin中,一個Kotlin註解類本身自己也能夠被註解,能夠給註解類加註解。咱們把這種註解稱爲元註解,能夠把它理解爲一種基本的註解,能夠把它理解爲一種特殊的標籤,用於標註標籤的標籤。框架

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

@Target元註解

  • 一、介紹

Target顧名思義就是目標對象,也就是這個標籤做用於哪些代碼中目標對象,能夠同時指定多個做用的目標對象。ide

  • 二、源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//能夠給標籤本身貼標籤
@MustBeDocumented
//註解類構造器參數是個vararg不定參數修飾符,因此能夠同時指定多個做用的目標對象
public annotation class Target(vararg val allowedTargets: AnnotationTarget)
複製代碼
  • 三、@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元註解

  • 一、介紹

Retention對應的英文意思是保留期,當它應用於一個註解上表示該註解保留存活時間,無論是Java仍是Kotlin通常都有三種時期: 源代碼時期(SOURCE)編譯時期(BINARY)運行時期(RUNTIME)

  • 二、源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象是註解類
public annotation class Retention(val value: AnnotationRetention = AnnotationRetention.RUNTIME)//接收一個參數,該參數有個默認值,默認是保留在運行時期
複製代碼
  • 三、@Retention元註解的取值

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

public enum class AnnotationRetention {
    SOURCE,//源代碼時期(SOURCE): 註解不會存儲在輸出class字節碼中
    BINARY,//編譯時期(BINARY): 註解會存儲出class字節碼中,可是對反射不可見
    RUNTIME//運行時期(RUNTIME): 註解會存儲出class字節碼中,也會對反射可見, 默認是RUNTIME
}
複製代碼

@MustBeDocumented元註解

  • 一、介紹

該註解比較簡單主要是爲了標註一個註解類做爲公共API的一部分,而且能夠保證該註解在生成的API文檔中存在。

  • 二、源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是註解類
public annotation class MustBeDocumented
複製代碼

@Repeatable元註解

  • 一、介紹

這個註解決定標註的註解在一個註解在一個代碼元素上能夠應用兩次或兩次以上。

  • 二、源碼定義
@Target(AnnotationTarget.ANNOTATION_CLASS)//目標對象只能是註解類
public annotation class Repeatable
複製代碼

爲啥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元註解

關於這個問題實際上在Kotlin官網的discuss中就有人提出了這個問題,具體感興趣的能夠去看看:Inherited annotations and other reflections enchancements. 這裏大概說下緣由,咱們都知道在Java中,沒法找到子類方法是否重寫了父類的方法。所以不能繼承父類方法的註解。然而Kotlin目前不須要支持這個@Inherited元註解,由於Kotlin能夠作到,若是反射提供了override標記並且很容易作到。

6、註解的使用場景

  • 一、提供信息給編譯器: 編譯器能夠利用註解來處理一些,好比一些警告信息,錯誤等
  • 二、編譯階段時處理: 利用註解信息來生成一些代碼,在Kotlin生成代碼很是常見,一些內置的註解爲了與Java API的互操做性,每每藉助註解在編譯階段生成一些額外的代碼。
  • 三、運行時處理: 某些註解能夠在程序運行時,經過反射機制獲取註解信息來處理一些程序邏輯。

7、Kotlin中的預置註解

在Kotlin中最大的一個特色就是能夠和Java作到極高的互操做性,咱們知道Kotlin的語法和Java語法仍是有很大的不一樣,要想作到與Java作到很大兼容性可能須要攜帶一些額外信息,供編譯器或者運行時作相似兼容轉換。其中註解就起到了很大的做用,在Kotlin內置不少個註解爲了解決Java中的調用Kotlin API的一些調用習慣和控制API的調用。它們就是Kotlin中的@Jvm系列的註解,我們一一來看下它們都有哪些。

@JvmDefault

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

  • 一、做用

咱們都知道在Kotlin中的接口中能夠增長非抽象成員,那麼該註解就是爲非抽象的接口成員生成默認的方法。

使用-Xjvm-default = enable,會爲每一個@JvmDefault註解標註的方法生成接口中的默認方法。在此模式下,使用@JvmDefault註解現有方法可能會破壞二進制兼容性,由於它將有效地從DefaultImpls類中刪除該方法。

使用-Xjvm-default = compatibility,除了默認接口方法以外,還會生成兼容性訪問器。在DefaultImpls類中,它經過合成訪問器調用默認接口方法。在此模式下,使用@JvmDefault註解現有方法是二進制兼容的,但在字節碼中會產生更多方法。從接口成員中移除此註解會使在兩種模式中的二進制不兼容性發生變化。

  • 二、源碼定義
@SinceKotlin("1.2")//從Kotlin的1.2版本第一次出現該註解
@RequireKotlin("1.2.40", versionKind = RequireKotlinVersionKind.COMPILER_VERSION)
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)//目標對象是函數和屬性
annotation class JvmDefault
複製代碼
  • 三、使用註解先後反編譯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

  • 一、做用

能夠應用於一個字段,把這個屬性暴露成一個沒有訪問器的公有Java字段;以及Companion Object對象中。

  • 二、源碼定義
@Target(AnnotationTarget.FIELD)//做用對象是字段,包括屬性的幕後字段
@Retention(AnnotationRetention.BINARY)//註解保留期是源碼階段
@MustBeDocumented
public actual annotation class JvmField
複製代碼
  • 三、註解使用

使用場景一:

咱們知道在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

  • 一、做用

該註解主要是爲了生成多文件的類

  • 二、源碼定義
@Target(AnnotationTarget.FILE)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmMultifileClass()
複製代碼
  • 三、註解使用

在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

  • 一、做用

將改變由Kotlin默認生成的Java方法、字段或類名

  • 二、源碼定義
//做用的目標有: 函數、屬性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的名稱
複製代碼
  • 三、註解使用
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

  • 一、做用

指導Kotlin編譯器爲帶默認參數值的函數(包括構造函數)生成多個重載函數。

  • 二、源碼定義
//做用對象是函數和構造函數
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmOverloads()
複製代碼
  • 三、註解使用

該註解使用最多就是用於帶默認值函數的重載,在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

  • 一、做用

更改從使用該註解標註的文件生成的.class文件的JVM包的徹底限定名稱。 這不會影響Kotlin客戶端在此文件中查看聲明的方式,但Java客戶端和其餘JVM語言客戶端將看到類文件,就好像它是在指定的包中聲明的那樣。 若是使用此批註對文件進行批註,則它只能包含函數,屬性和類型聲明,但不能包含。

  • 二、源碼定義
@Target(AnnotationTarget.FILE)//做用於文件
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@SinceKotlin("1.2")//Kotlin1.2版本加入
internal annotation class JvmPackageName(val name: String)
複製代碼
  • 三、註解使用
//以Collection源碼爲例
@file:kotlin.jvm.JvmPackageName("kotlin.collections.jdk8")

package kotlin.collections
複製代碼

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

@JvmStatic

  • 一、做用

能被用在對象聲明或者Companion object伴生對象的方法上,把它們暴露成一個Java的靜態方法

  • 二、源碼定義
//做用於函數、屬性、屬性的setter和getter
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@MustBeDocumented
@OptionalExpectation
public expect annotation class JvmStatic()
複製代碼
  • 三、註解使用

@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

  • 一、做用

用於指示編譯器生成或省略類型參數的通配符,JvmSuppressWildcards用於參數的泛型是否生成或省略通配符,而JvmWildcard用於返回值的類型是否生成或省略通配符

  • 二、源碼定義
//做用於類、函數、屬性、類型
@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
複製代碼
  • 三、註解使用
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

  • 一、做用

它在生成的類文件中將適當的元素標記爲合成,而且編譯器標記爲合成的任何元素都將沒法從Java語言中訪問。

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

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

  • 三、合成屬性能作什麼?

它通常用於支持代碼生成,容許編譯器生成不該向其餘開發人員公開但須要支持實際公開接口所需的字段和方法。咱們能夠將其視爲超越private或protected級別。

  • 四、源碼定義
//做用於函數、屬性的setter,getter以及字段
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FIELD)
@OptionalExpectation
public expect annotation class JvmSynthetic()
複製代碼
  • 五、註解使用
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

  • 一、做用

用於Kotlin中的函數,屬性的setter或getter函數,構造器函數拋出異常

  • 二、源碼定義
//做用於函數、屬性的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不定參數,能夠同時指定一個或多個異常
複製代碼
  • 三、註解使用
@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邂逅設計模式系列:

數據結構與算法系列:

翻譯系列:

原創系列:

Effective Kotlin翻譯系列

實戰系列:

相關文章
相關標籤/搜索