前言
Kotlin
為了能和Java
更加友好的進(jìn)行交互(PY),提供了一些注解參數(shù)使得Java調(diào)用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ā)過程中最常用到的一些注解缤骨,如果你有疑問歡迎留言交流~~