kotlin

Kotlin JVM常用注解参数解析

2018-10-23  本文已影响625人  旅旅人
前言

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

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

只有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上也能在接口上提供具体方法的强大功能,缺点是:

为了解决这个问题,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语法。以下是一些注意事项:


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
)

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

上一篇下一篇

猜你喜欢

热点阅读