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
)
复制代码
以上就是平常开发过程当中最经常使用到的一些注解,若是你有疑问欢迎留言交流~~