Kotlin
爲了能和Java
更加友好的進行交互(PY),提供了一些註解參數使得Java調用Kotlin時更加方便和友好.html
Kotlin官方註解地址java
JvmDefault
JvmField
JvmMultifileClass
JvmName
JvmOverloads
JvmStatic
Strictfp
Synchronized
Volatile
Transient
指定爲非抽象Kotlin接口成員生成JVM默認方法。 此註解的用法須要指定編譯參數: -Xjvm-default=enable
或者-Xjvm-default=compatibility
。api
-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上也能在接口上提供具體方法的強大功能,缺點是:
DefaultImpls
類,這是一個很神奇的事情....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
調用來完成.使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
這個註解的主要用途就是告訴編譯器生成的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");
}
}
複製代碼
這個註解咱們用來應對各類類名修改之後的兼容性問題
這個註解讓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類中添加方法更加靈活和方便
告訴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的默認參數的特性,使用此註解來生成對應的重載方法。
重載的規則是順序重載,只有有默認值的參數會參與重載.
對函數使用該註解,kotlin編譯器將生成另外一個靜態方法
對屬性使用該註解,kotlin編譯器將生成其餘的setter和getter方法
這個註解的做用其實就是消除Java調用Kotlin的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的交互更加友好了~
將從註釋函數生成的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
}
複製代碼
將從帶註釋的函數生成的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)
}
}
}
複製代碼
將帶註釋屬性的JVM支持字段標記爲volatile,這意味着對此字段的寫入當即對其餘線程可見.
對應Java中的volatile
關鍵字
使用場景以下
//不能對val變量加註解
@Volatile
var volatileStr: String = "volatile"
複製代碼
將帶註釋的屬性的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
)
複製代碼
以上就是平常開發過程當中最經常使用到的一些註解,若是你有疑問歡迎留言交流~~