博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Kotlin JVM常用注解参数解析
阅读量:6549 次
发布时间:2019-06-24

本文共 12296 字,大约阅读时间需要 40 分钟。

前言

Kotlin为了能和Java更加友好的进行交互(PY),提供了一些注解参数使得Java调用Kotlin时更加方便和友好.

今天我们来学习和理解这些常用的注解:JvmDefault JvmField JvmMultifileClass JvmName JvmOverloads JvmStatic Strictfp Synchronized Volatile Transient

JvmDefault

指定为非抽象Kotlin接口成员生成JVM默认方法。 此注解的用法需要指定编译参数: -Xjvm-default=enable或者-Xjvm-default=compatibility

  • -Xjvm-default=enable :仅为每个@JvmDefault方法生成接口中的默认方法。在此模式下,使用@JvmDefault注释现有方法可能会破坏二进制兼容性,因为它将有效地从DefaultImpls类中删除该方法。
  • -Xjvm-default=compatibility:除了默认的接口方法,在生成的兼容性访问DefaultImpls类,调用通过合成访问默认接口方法。在此模式下,使用@JvmDefault注释现有方法是二进制兼容的,但在字节码中会产生更多方法。

从接口成员中移除此注解会使在两种模式中的二进制不兼容性发生变化。

只有JVM目标字节码版本1.8(-jvm-target 1.8)或更高版本才能生成默认方法。


让我们试一试这个注解看看怎么使用

首先我们看不加注解的情况:

interface Animal {  var name: String?  var age: Int?  fun getDesc() = name + "今年已经" + age + "岁啦~"}class Dog(override var name: String?, override var age: Int?) : Animalfun main(args: Array
?) { 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();   }}复制代码

从上述代码可以发现,当我们去调用接口的默认方法时,其实是调用了匿名静态内部类的方法。

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?) : Animalfun main(args: Array
?) { 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.sssvar 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:JvmMultifileClasspackage com.example.maqiang.sssfun getA() = "A"//B.kt@file:JvmName("Utils")@file:JvmMultifileClasspackage com.example.maqiang.sssfun 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代码

@JvmOverloadsfun 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变量加注解@Volatilevar volatileStr: String = "volatile"复制代码

Transient

将带注释的属性的JVM支持字段标记为transient,表示它不是对象的默认序列化形式的一部分。

对应Java中的transient关键字

使用场景如下:

//:Serializabledata class XBean(    val name: String?,    val age: Int?,    //不参与序列化    @Transient    val male: Boolean = true): Serializable//Parcelize(目前还是实验性功能 需要在gradle中配置开启 experimental = true)@Parcelizedata class XBean(    val name: String?,    val age: Int?,    //不参与序列化    @Transient    val male: Boolean = true)复制代码

以上就是日常开发过程中最常用到的一些注解,如果你有疑问欢迎留言交流~~

转载于:https://juejin.im/post/5bcf3c246fb9a05d28738801

你可能感兴趣的文章
项目实践中Linux集群的总结和思考
查看>>
关于使用Android NDK编译ffmpeg
查看>>
监控MySQL主从同步是否异常并报警企业案例模拟
查看>>
zabbix从2.2.3升级到最新稳定版3.2.1
查看>>
我有一个网站,想提高点权重
查看>>
浅谈(SQL Server)数据库中系统表的作用
查看>>
微软邮件系统Exchange 2013系列(七)创建发送连接器
查看>>
程序员杂记系列
查看>>
【树莓派】制作树莓派所使用的img镜像(一)
查看>>
理解网站并发量
查看>>
spring整合elasticsearch之环境搭建
查看>>
TensorFlow 架构与设计-编程模型【转】
查看>>
如何运行Struts2官网最新Demo?
查看>>
'ascii' codec can't decode byte 0xe6 in position 0: ordinal not in range(128)
查看>>
XDebug 教程
查看>>
js 去html 标签
查看>>
好久不见
查看>>
小tips:JS中的children和childNodes
查看>>
二叉树的遍历
查看>>
Oracle的FIXED_DATE参数
查看>>