Kotlin JVM經常使用註解參數解析

前言

Kotlin爲了能和Java更加友好的進行交互(PY),提供了一些註解參數使得Java調用Kotlin時更加方便和友好.html

Kotlin官方註解地址java

今天咱們來學習和理解這些經常使用的註解:JvmDefault JvmField JvmMultifileClass JvmName JvmOverloads JvmStatic Strictfp Synchronized Volatile Transient

JvmDefault

指定爲非抽象Kotlin接口成員生成JVM默認方法。 此註解的用法須要指定編譯參數: -Xjvm-default=enable或者-Xjvm-default=compatibilityapi

  • -Xjvm-default=enable :僅爲每一個@JvmDefault方法生成接口中的默認方法。在此模式下,使用@JvmDefault註釋現有方法可能會破壞二進制兼容性,由於它將有效地從DefaultImpls類中刪除該方法。
  • -Xjvm-default=compatibility:除了默認的接口方法,在生成的兼容性訪問DefaultImpls類,調用經過合成訪問默認接口方法。在此模式下,使用@JvmDefault註釋現有方法是二進制兼容的,但在字節碼中會產生更多方法。

從接口成員中移除此註解會使在兩種模式中的二進制不兼容性發生變化。併發

只有JVM目標字節碼版本1.8(-jvm-target 1.8)或更高版本才能生成默認方法。jvm


讓咱們試一試這個註解看看怎麼使用ide

首先咱們看不加註解的狀況:函數

interface Animal {
  var name: String?
  var age: Int?
  fun getDesc() = name + "今年已經" + age + "歲啦~"

}

class Dog(override var name: String?, override var age: Int?) : Animal

fun main(args: Array<String>?) {
  Dog("小白", 3).getDesc()
}
複製代碼

字節碼轉換成Java代碼之後是下面這個樣子:學習

public interface Animal {
   @Nullable
   String getName();

   void setName(@Nullable String var1);

   @Nullable
   Integer getAge();

   void setAge(@Nullable Integer var1);

   @NotNull
   String getDesc();

   public static final class DefaultImpls {
      @NotNull
      public static String getDesc(Animal $this) {
         return $this.getName() + "今年已經" + $this.getAge() + "歲啦~";
      }
   }
}

public final class Dog implements Animal {
   @Nullable
   private String name;
   @Nullable
   private Integer age;

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

   public void setName(@Nullable String var1) {
      this.name = var1;
   }

   @Nullable
   public Integer getAge() {
      return this.age;
   }

   public void setAge(@Nullable Integer var1) {
      this.age = var1;
   }

   public Dog(@Nullable String name, @Nullable Integer age) {
      this.name = name;
      this.age = age;
   }

   @NotNull
   public String getDesc() {
      return Animal.DefaultImpls.getDesc(this);
   }
}

public final class JvmKt {
   public static final void main(@Nullable String[] args) {
      (new Dog("小白", 3)).getDesc();
   }
}

複製代碼

從上述代碼能夠發現,當咱們去調用接口的默認方法時,實際上是調用了匿名靜態內部類的方法。gradle

Kotlin建立了一個靜態內部類,調用DefaultImpls它來存儲方法的默認實現,這些方法都是靜態的,並使用 「Self」 接收器類型來模擬屬於對象的方法。而後,對於擴展該接口的每種類型,若是類型沒有實現方法自己,則在編譯時,Kotlin將經過調用將方法鏈接到默認實現。優化

這樣的實現有好處也有壞處,好處是它能夠在Java 8以前的JVM上也能在接口上提供具體方法的強大功能,缺點是:

  • 它與Java的處理方式不兼容,致使了互操做性很混亂.咱們能夠在Java代碼中直接調用DefaultImpls類,這是一個很神奇的事情....
  • Java8中存在默認方法的主要緣由之一是可以在沒必要觸及每一個子類(例如添加Collection.stream())的狀況下向接口添加方法.Kotlin實現不支持這個,由於必須在每一個具體類型上生成默認調用。向接口添加新方法致使必須從新編譯每一個實現者.

爲了解決這個問題,Kotlin推出了@JvmDefault來優化這種狀況. 接下來咱們看看加註解之後是什麼樣子: gradle文件添加編譯配置

-jvm-target=1.8 -Xjvm-default=enable
複製代碼

Kotlin代碼

interface Animal {
 var name: String?
 var age: Int?
 @JvmDefault
 fun getDesc() = name + "今年已經" + age + "歲啦~"

}

class Dog(override var name: String?, override var age: Int?) : Animal

fun main(args: Array<String>?) {

 Dog("小白", 3).getDesc()
}
複製代碼

對應的Java代碼

public interface Animal {
   @Nullable
   String getName();

   void setName(@Nullable String var1);

   @Nullable
   Integer getAge();

   void setAge(@Nullable Integer var1);

   @JvmDefault
   @NotNull
   default String getDesc() {
      return this.getName() + "今年已經" + this.getAge() + "歲啦~";
   }
}

public final class Dog implements Animal {
   @Nullable
   private String name;
   @Nullable
   private Integer age;

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

   public void setName(@Nullable String var1) {
      this.name = var1;
   }

   @Nullable
   public Integer getAge() {
      return this.age;
   }

   public void setAge(@Nullable Integer var1) {
      this.age = var1;
   }

   public Dog(@Nullable String name, @Nullable Integer age) {
      this.name = name;
      this.age = age;
   }
}
public final class JvmKt {
   public static final void main(@Nullable String[] args) {
      (new Dog("小白", 3)).getDesc();
   }
}

複製代碼

咱們能夠看到使用註解之後消除了匿名靜態內部類去橋接實現默認方法,這樣的話,Java調用Kotlind接口的默認方法時就和調用Java接口的默認方法基本一致了.

注意,除了更改編譯器標誌外,還使用Kotlin 1.2.50添加了兼容模式。兼容性標誌(-Xjvm-default=compatibility)專門用於保留與現有Kotlin類的二進制兼容性,同時仍然可以轉移到Java 8樣式的默認方法。在考慮生成的指向靜態橋接方法的其餘項目時,此標誌特別有用。

爲實現此目的,Kotlin編譯器使用類文件技巧invokespecial來調用默認接口方法,同時仍保留DefaultImpls橋接類。咱們一塊兒來看看是什麼樣子的:

public interface Animal {
   @JvmDefault
   @NotNull
   default String getDesc() {
      return "喵喵喵喵";
   }

   public static final class DefaultImpls {
      @NotNull
      public static String getDesc(Animal $this) {
         return $this.getDesc();
      }
   }
}

//新編譯的狀況下
public final class Dog implements Animal {

}

//在其餘一些項目中,已經編譯存在
public final class OldDog implements Animal {
   @NotNull
   public String getDesc() {
      return Animal.DefaultImpls.getDesc(this);
   }
}

複製代碼

這裏有一個很好的解壓縮,特別是由於這不是有效的Java語法。如下是一些注意事項:

  • 在接口上生成默認方法,就像咱們剛剛使用時同樣 enable
  • 新編譯的類,如Dog,將直接使用Java 8樣式的默認接口。
  • OldDog這樣的現有編譯代碼仍然能夠在二進制級別工做,由於它指向了DefaultImpls類。
  • DefaultImpls方法的實現不能在真正的Java源來表示,由於它是相似於調用<init><super>; 該方法必須在提供的實例上調用Animal接口上的getDesc方法。若是它只是調用「getDesc()」,它將致使舊類型(OldDog.getDesc() -> DefaultImpls.getDesc() -> OldDog.getDesc())的堆棧溢出。相反,它必須直接調用接口:OldDog.getDesc() -> DefaultImpls.getDesc() -> Animal.getDesc(),而且只能經過接口方法invokespecial調用來完成.

JvmField

使Kotlin編譯器再也不對該字段生成getter/setter並將其做爲公開字段(public)

使用場景以下:

kotlin代碼

class Bean(
  @JvmField
  var name:String?,
  var age:Int
)
複製代碼

對應的Java代碼

public final class Bean {
   @JvmField
   @Nullable
   public String name;
   private int age;

   public final int getAge() {
      return this.age;
   }

   public final void setAge(int var1) {
      this.age = var1;
   }

   public Bean(@Nullable String name, int age) {
      this.name = name;
      this.age = age;
   }
}
複製代碼

對比很明顯,被註解的字段屬性修飾符會從private變成public


JvmName

這個註解的主要用途就是告訴編譯器生成的Java類或者方法的名稱

使用場景以下:

Koltin代碼

@file:JvmName("JavaClass")

package com.example.maqiang.sss

var kotlinField: String? = null
  //修改屬性的set方法名
  @JvmName("setJavaField")
  set(value) {
    field = value
  }

//修改普通的方法名
@JvmName("JavaFunction")
fun kotlinFunction() {
}
複製代碼

對應的Java代碼:

public final class JavaClass {
   @Nullable
   private static String kotlinField;

   @Nullable
   public static final String getKotlinField() {
      return kotlinField;
   }

   @JvmName(
      name = "setJavaField"
   )
   public static final void setJavaField(@Nullable String value) {
      kotlinField = value;
   }

   @JvmName(
      name = "JavaFunction"
   )
   public static final void JavaFunction() {
   }
}
複製代碼

Java調用kotlin代碼

public class JavaJvm{
  public static void main(String[] args) {
    //類名和方法都是註解修改之後的
    JavaClass.JavaFunction();
    JavaClass.getKotlinField();
    JavaClass.setJavaField("java");
  }
}
複製代碼

這個註解咱們用來應對各類類名修改之後的兼容性問題


JvmMultifileClass

這個註解讓Kotlin編譯器生成一個多文件類,該文件具備在此文件中聲明的頂級函數和屬性做爲其中的一部分,JvmName註解提供了相應的多文件的名稱.

使用場景解析:

Kotlin代碼:

//A.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package com.example.maqiang.sss
fun getA() = "A"

//B.kt
@file:JvmName("Utils")
@file:JvmMultifileClass
package com.example.maqiang.sss
fun getB() = "B"
複製代碼

Java調用Kotlin的頂級函數

public class JavaJvm {
  public static void main(String[] args) {
    Utils.getA();
    Utils.getB();
  }
}
複製代碼

咱們能夠看到使用註解之後將A和B文件中的方法合在了一個Utils類中,這個註解能夠消除咱們去手動建立一個Utils類,向Utils類中添加方法更加靈活和方便


JvmOverloads

告訴Kotlin編譯器爲此函數生成替換默認參數值的重載

使用場景以下:

kotlin代碼

@JvmOverloads
fun goToActivity( context: Context?, url: String?, bundle: Bundle? = null, requestCode: Int = -1 ) {
}
複製代碼

對應的Java代碼

public final class AKt {
   @JvmOverloads
   public static final void goToActivity(@Nullable Context context, @Nullable String url, @Nullable Bundle bundle, int requestCode) {
   }

   // $FF: synthetic method
   // $FF: bridge method
   @JvmOverloads
   public static void goToActivity$default(Context var0, String var1, Bundle var2, int var3, int var4, Object var5) {
      if ((var4 & 4) != 0) {
         var2 = (Bundle)null;
      }

      if ((var4 & 8) != 0) {
         var3 = -1;
      }

      goToActivity(var0, var1, var2, var3);
   }

   @JvmOverloads
   public static final void goToActivity(@Nullable Context context, @Nullable String url, @Nullable Bundle bundle) {
      goToActivity$default(context, url, bundle, 0, 8, (Object)null);
   }

   @JvmOverloads
   public static final void goToActivity(@Nullable Context context, @Nullable String url) {
      goToActivity$default(context, url, (Bundle)null, 0, 12, (Object)null);
   }
複製代碼

咱們能夠看到爲了能讓Java享受到Koltin的默認參數的特性,使用此註解來生成對應的重載方法。

重載的規則是順序重載,只有有默認值的參數會參與重載.


JvmStatic

對函數使用該註解,kotlin編譯器將生成另外一個靜態方法

對屬性使用該註解,kotlin編譯器將生成其餘的setter和getter方法

這個註解的做用其實就是消除Java調用Kotlin的companion object對象時不能直接調用其靜態方法和屬性的問題.

注意:此註解只能在`companion object`中使用

使用場景對比

companion object中未使用註解的狀況下

class A {
  companion object {
    var string: String? = null
    fun hello() = "hello,world"
  }
}
複製代碼

對應的Java代碼

public final class A {
   @Nullable
   private static String string;
   public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);
   
   public static final class Companion {
      @Nullable
      public final String getString() {
         return A.string;
      }

      public final void setString(@Nullable String var1) {
         A.string = var1;
      }

      @NotNull
      public final String hello() {
         return "hello,world";
      }

      private Companion() {
      }

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

咱們能夠看到這個時候Java去調用kotlin的伴生對象的方法和屬性時候須要經過Companion.

companion object中使用註解的狀況下

class A {
  companion object {
    @JvmStatic
    var string: String? = null
    @JvmStatic
    fun hello() = "hello,world"
  }
}
複製代碼

對應的Java代碼

public final class A {
   @Nullable
   private static String string;
   public static final A.Companion Companion = new A.Companion((DefaultConstructorMarker)null);

   @Nullable
   public static final String getString() {
      A.Companion var10000 = Companion;
      return string;
   }

   public static final void setString(@Nullable String var0) {
      A.Companion var10000 = Companion;
      string = var0;
   }

   @JvmStatic
   @NotNull
   public static final String hello() {
      return Companion.hello();
   }

   public static final class Companion {
      /** @deprecated */
      // $FF: synthetic method
      @JvmStatic
      public static void string$annotations() {
      }

      @Nullable
      public final String getString() {
         return A.string;
      }

      public final void setString(@Nullable String var1) {
         A.string = var1;
      }

      @JvmStatic
      @NotNull
      public final String hello() {
         return "hello,world";
      }

      private Companion() {
      }

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

咱們能夠看到,雖然Companion這個靜態內部類還在,可是Java如今能夠直接調用對應的靜態方法和屬性了.

註解使用前和註解使用後的Java調用對比

public class JavaJvm {
  public static void main(String[] args) {
    //使用註解前
    A.Companion.hello();
    A.Companion.getString();
    A.Companion.setString("hello,kotlin");
    //使用註解後
    A.hello();
    A.getString();
    A.setString("hello,kotlin");
  }
}
複製代碼

明顯註解使Java和kotlin的交互更加友好了~


Strictfp

將從註釋函數生成的JVM方法標記爲strictfp,意味着須要限制在方法內執行的浮點運算的精度,以實現更好的可移植性。

對應Java中的strictfp關鍵字

使用場景以下:

//能夠用在構造函數、屬性的getter/setter、普通方法
//官網的Target中有class,可是實際使用並不能對class加註解
class JvmAnnotation @Strictfp constructor() {
    var a: Float = 0.0f
        @Strictfp
        get() {
            return 1f
        }
        @Strictfp
        set(value) {
            field = value
        }
    @Strictfp
    fun getFloatValue(): Float = 0.0f
    
}
複製代碼

Synchronized

將從帶註釋的函數生成的JVM方法標記爲synchronized,這意味着該方法將受到定義該方法的實例(或者對於靜態方法,類)的監視器的多個線程的併發執行的保護。

對應Java中的synchronized關鍵字

使用場景以下

class JvmAnnotation {
    var syn: String = ""
        @Synchronized
        get() {
            return "test"
        }
        @Synchronized
        set(value) {
            field = value
        }
    @Synchronized
    fun getSynString(): String = "test"
    
    fun setSynString(str:String){
        //注意這裏使用的是內斂函數來實現的對代碼塊加鎖
        synchronized(this){
            println(str)
        }
    }

}
複製代碼

Volatile

將帶註釋屬性的JVM支持字段標記爲volatile,這意味着對此字段的寫入當即對其餘線程可見.

對應Java中的volatile關鍵字

使用場景以下

//不能對val變量加註解
@Volatile
var volatileStr: String = "volatile"
複製代碼

Transient

將帶註釋的屬性的JVM支持字段標記爲transient,表示它不是對象的默認序列化形式的一部分。

對應Java中的transient關鍵字

使用場景以下:

//:Serializable
data class XBean(
    val name: String?,
    val age: Int?,
    //不參與序列化
    @Transient
    val male: Boolean = true
): Serializable

//Parcelize(目前仍是實驗性功能 須要在gradle中配置開啓 experimental = true)
@Parcelize
data class XBean(
    val name: String?,
    val age: Int?,
    //不參與序列化
    @Transient
    val male: Boolean = true
)
複製代碼

以上就是平常開發過程當中最經常使用到的一些註解,若是你有疑問歡迎留言交流~~

相關文章
相關標籤/搜索