Kotlin JVM常用注解參數(shù)解析

前言

Kotlin為了能和Java更加友好的進(jìn)行交互(PY),提供了一些注解參數(shù)使得Java調(diào)用Kotlin時更加方便和友好.

Kotlin官方注解地址

今天我們來學(xué)習(xí)和理解這些常用的注解:JvmDefault JvmField JvmMultifileClass JvmName JvmOverloads JvmStatic Strictfp Synchronized Volatile Transient

JvmDefault

指定為非抽象Kotlin接口成員生成JVM默認(rèn)方法包归。
此注解的用法需要指定編譯參數(shù): -Xjvm-default=enable或者-Xjvm-default=compatibility灵奖。

  • -Xjvm-default=enable :僅為每個@JvmDefault方法生成接口中的默認(rèn)方法咬扇。在此模式下宵蛀,使用@JvmDefault注釋現(xiàn)有方法可能會破壞二進(jìn)制兼容性,因為它將有效地從DefaultImpls類中刪除該方法背犯。
  • -Xjvm-default=compatibility:除了默認(rèn)的接口方法,在生成的兼容性訪問DefaultImpls類,調(diào)用通過合成訪問默認(rèn)接口方法吞歼。在此模式下,使用@JvmDefault注釋現(xiàn)有方法是二進(jìn)制兼容的塔猾,但在字節(jié)碼中會產(chǎn)生更多方法篙骡。

從接口成員中移除此注解會使在兩種模式中的二進(jìn)制不兼容性發(fā)生變化。

只有JVM目標(biāo)字節(jié)碼版本1.8(-jvm-target 1.8)或更高版本才能生成默認(rèn)方法丈甸。


讓我們試一試這個注解看看怎么使用

首先我們看不加注解的情況:

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

}

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

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

字節(jié)碼轉(zhuǎn)換成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() + "今年已經(jīng)" + $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();
   }
}

從上述代碼可以發(fā)現(xiàn),當(dāng)我們?nèi)フ{(diào)用接口的默認(rèn)方法時,其實是調(diào)用了匿名靜態(tài)內(nèi)部類的方法糯俗。

Kotlin創(chuàng)建了一個靜態(tài)內(nèi)部類,調(diào)用DefaultImpls它來存儲方法的默認(rèn)實現(xiàn)睦擂,這些方法都是靜態(tài)的,并使用" Self "接收器類型來模擬屬于對象的方法得湘。然后,對于擴(kuò)展該接口的每種類型顿仇,如果類型沒有實現(xiàn)方法本身淘正,則在編譯時,Kotlin將通過調(diào)用將方法連接到默認(rèn)實現(xiàn)夺欲。

這樣的實現(xiàn)有好處也有壞處跪帝,好處是它可以在Java 8之前的JVM上也能在接口上提供具體方法的強(qiáng)大功能,缺點是:

  • 它與Java的處理方式不兼容,導(dǎo)致了互操作性很混亂.我們可以在Java代碼中直接調(diào)用DefaultImpls類些阅,這是一個很神奇的事情....
  • Java8中存在默認(rèn)方法的主要原因之一是能夠在不必觸及每個子類(例如添加Collection.stream())的情況下向接口添加方法.Kotlin實現(xiàn)不支持這個伞剑,因為必須在每個具體類型上生成默認(rèn)調(diào)用。向接口添加新方法導(dǎo)致必須重新編譯每個實現(xiàn)者.

為了解決這個問題,Kotlin推出了@JvmDefault來優(yōu)化這種情況.
接下來我們看看加注解以后是什么樣子:
gradle文件添加編譯配置

-jvm-target=1.8 -Xjvm-default=enable

Kotlin代碼

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

}

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

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

對應(yīng)的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() + "今年已經(jīng)" + 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();
   }
}

我們可以看到使用注解以后消除了匿名靜態(tài)內(nèi)部類去橋接實現(xiàn)默認(rèn)方法市埋,這樣的話黎泣,Java調(diào)用Kotlind接口的默認(rèn)方法時就和調(diào)用Java接口的默認(rèn)方法基本一致了.

注意,除了更改編譯器標(biāo)志外缤谎,還使用Kotlin 1.2.50添加了兼容模式抒倚。兼容性標(biāo)志(-Xjvm-default=compatibility)專門用于保留與現(xiàn)有Kotlin類的二進(jìn)制兼容性,同時仍然能夠轉(zhuǎn)移到Java 8樣式的默認(rèn)方法坷澡。在考慮生成的指向靜態(tài)橋接方法的其他項目時托呕,此標(biāo)志特別有用。

為實現(xiàn)此目的频敛,Kotlin編譯器使用類文件技巧invokespecial來調(diào)用默認(rèn)接口方法项郊,同時仍保留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 {

}

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

這里有一個很好的解壓縮着降,特別是因為這不是有效的Java語法。以下是一些注意事項:

  • 在接口上生成默認(rèn)方法拗军,就像我們剛剛使用時一樣 enable
  • 新編譯的類任洞,如Dog蓄喇,將直接使用Java 8樣式的默認(rèn)接口。
  • OldDog這樣的現(xiàn)有編譯代碼仍然可以在二進(jìn)制級別工作交掏,因為它指向了DefaultImpls類妆偏。
  • DefaultImpls方法的實現(xiàn)不能在真正的Java源來表示,因為它是類似于調(diào)用<init><super>; 該方法必須在提供的實例上調(diào)用Animal接口上的getDesc方法耀销。如果它只是調(diào)用“getDesc()”楼眷,它將導(dǎo)致舊類型(OldDog.getDesc() -> DefaultImpls.getDesc() -> OldDog.getDesc())的堆棧溢出。相反熊尉,它必須直接調(diào)用接口:OldDog.getDesc() -> DefaultImpls.getDesc() -> Animal.getDesc()罐柳,并且只能通過接口方法invokespecial調(diào)用來完成.

JvmField

使Kotlin編譯器不再對該字段生成getter/setter并將其作為公開字段(public)

使用對比
kotlin代碼

class Bean(
  @JvmField
  var name:String?,
  var age:Int
)

對應(yīng)的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() {
}

對應(yīng)的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調(diào)用kotlin代碼

public class JavaJvm{
  public static void main(String[] args) {
    //類名和方法都是注解修改以后的
    JavaClass.JavaFunction();
    JavaClass.getKotlinField();
    JavaClass.setJavaField("java");
  }
}

這個注解我們用來應(yīng)對各種類名修改以后的兼容性問題


JvmMultifileClass

這個注解讓Kotlin編譯器生成一個多文件類狰住,該文件具有在此文件中聲明的頂級函數(shù)和屬性作為其中的一部分张吉,JvmName注解提供了相應(yīng)的多文件的名稱.

使用場景解析:
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調(diào)用Kotlin的頂級函數(shù)

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

我們可以看到使用注解以后將A和B文件中的方法合在了一個Utils類中,這個注解可以消除我們?nèi)ナ謩觿?chuàng)建一個Utils類,向Utils類中添加方法更加靈活和方便


JvmOverloads

告訴Kotlin編譯器為此函數(shù)生成替換默認(rèn)參數(shù)值的重載
使用場景如下:
kotlin代碼

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

對應(yīng)的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的默認(rèn)參數(shù)的特性催植,使用此注解來生成對應(yīng)的重載方法肮蛹。
重載的規(guī)則是順序重載,只有有默認(rèn)值的參數(shù)會參與重載.


JvmStatic

對函數(shù)使用該注解创南,kotlin編譯器將生成另一個靜態(tài)方法伦忠,
對屬性使用該注解,kotlin編譯器將生成其他的setter和getter方法
這個注解的作用其實就是消除Java調(diào)用Kotlin的companion object對象時不能直接調(diào)用其靜態(tài)方法和屬性的問題.
注意:此注解只能在companion object中使用

使用場景對比

companion object中未使用注解的情況下

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

對應(yīng)的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去調(diào)用kotlin的伴生對象的方法和屬性時候需要通過Companion.

companion object中使用注解的情況下

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

對應(yīng)的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這個靜態(tài)內(nèi)部類還在昆码,但是Java現(xiàn)在可以直接調(diào)用對應(yīng)的靜態(tài)方法和屬性了.

注解使用前和注解使用后的Java調(diào)用對比

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

將從注釋函數(shù)生成的JVM方法標(biāo)記為strictfp,意味著需要限制在方法內(nèi)執(zhí)行的浮點運算的精度邻储,以實現(xiàn)更好的可移植性赋咽。

對應(yīng)Java中的strictfp關(guān)鍵字
使用如下:

//可以用在構(gòu)造函數(shù)、屬性的getter/setter吨娜、普通方法
//官網(wǎng)的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

將從帶注釋的函數(shù)生成的JVM方法標(biāo)記為synchronized脓匿,這意味著該方法將受到定義該方法的實例(或者對于靜態(tài)方法,類)的監(jiān)視器的多個線程的并發(fā)執(zhí)行的保護(hù)宦赠。

對應(yīng)Java中的synchronized關(guān)鍵字

使用場景如下

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

}

Volatile

將帶注釋屬性的JVM支持字段標(biāo)記為volatile陪毡,這意味著對此字段的寫入立即對其他線程可見.

對應(yīng)Java中的volatile關(guān)鍵字

使用場景如下

//不能對val變量加注解
@Volatile
var volatileStr: String = "volatile"

Transient

將帶注釋的屬性的JVM支持字段標(biāo)記為transient,表示它不是對象的默認(rèn)序列化形式的一部分勾扭。

對應(yīng)Java中的transient關(guān)鍵字

使用場景如下:

//: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
)

以上就是日常開發(fā)過程中最常用到的一些注解缤骨,如果你有疑問歡迎留言交流~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市尺借,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌精拟,老刑警劉巖燎斩,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件虱歪,死亡現(xiàn)場離奇詭異,居然都是意外死亡栅表,警方通過查閱死者的電腦和手機(jī)笋鄙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怪瓶,“玉大人萧落,你說我怎么就攤上這事∠捶。” “怎么了找岖?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長敛滋。 經(jīng)常有香客問我许布,道長,這世上最難降的妖魔是什么绎晃? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任蜜唾,我火速辦了婚禮,結(jié)果婚禮上庶艾,老公的妹妹穿的比我還像新娘袁余。我一直安慰自己,他們只是感情好咱揍,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布颖榜。 她就那樣靜靜地躺著,像睡著了一般述召。 火紅的嫁衣襯著肌膚如雪朱转。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天积暖,我揣著相機(jī)與錄音藤为,去河邊找鬼。 笑死夺刑,一個胖子當(dāng)著我的面吹牛缅疟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遍愿,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼存淫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了沼填?” 一聲冷哼從身側(cè)響起桅咆,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坞笙,沒想到半個月后岩饼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荚虚,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年籍茧,在試婚紗的時候發(fā)現(xiàn)自己被綠了版述。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡寞冯,死狀恐怖渴析,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吮龄,我是刑警寧澤俭茧,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站螟蝙,受9級特大地震影響恢恼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜胰默,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一场斑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牵署,春花似錦漏隐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至取具,卻和暖如春脖隶,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背暇检。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工产阱, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人块仆。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓构蹬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悔据。 傳聞我的和親對象是個殘疾皇子庄敛,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言 人生苦多,快來 Kotlin 科汗,快速學(xué)習(xí)Kotlin藻烤! 什么是Kotlin? Kotlin 是種靜態(tài)類型編程...
    任半生囂狂閱讀 26,218評論 9 118
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,331評論 25 707
  • Kotlin is 100% interoperable with Java? and Android? 在前面的...
    JackChen1024閱讀 6,426評論 1 15
  • 2017.8.3 周四 李彤 月芽寶貝41天 1.我怎么如此幸運呢衙四?發(fā)現(xiàn)自己這幾天發(fā)現(xiàn)月芽寶貝開始長睫毛了呢斩狱,麻麻...
    樸落閱讀 167評論 0 0
  • 天倫之樂 紅蔚 天倫之樂是一種家庭幸福和睦有愛的歡樂享受。是在守孝悌,懂理解依许,善溝通,彼此尊重缀蹄,寬容峭跳,自由,快樂的...
    紅蔚閱讀 894評論 1 3