Kotlin JVM常用注解参数解析

前言

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

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?) : 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();
   }
}

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

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?) : 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调用来完成.

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.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");
  }
}

这个注解我们用来应对各种类名修改以后的兼容性问题


JvmMultifileClass

这个注解让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类中添加方法更加灵活和方便


JvmOverloads

告诉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的默认参数的特性,使用此注解来生成对应的重载方法。
重载的规则是顺序重载,只有有默认值的参数会参与重载.


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变量加注解
@Volatile
var volatileStr: String = "volatile"

Transient

将带注释的属性的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
)

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容