Kotlin常用注解

作者: 竖起大拇指 | 来源:发表于2022-03-07 17:07 被阅读0次

    1.@JvmName

    这个注解可以改变字节码中生成的类名或方法名称,如果作用在顶级作用域(文件中),则会改变生成对应Java类的名称。如果作用在方法上,则会改变生成对应Java方法的名称。

    Test.kt

    @file:JvmName("FooKt")
    
    @JvmName("foo1")
    fun foo() {
        println("Hello, Jvm...")
    }
    

    在Kotlin语言中,foo是一个全局方法,为了兼容Java字节码,实际会根据文件名生成对应的Java类TestKt.java,这是Kotlin编译器的一个隐藏规则。

    而添加了上述注解之后,生成的类名与方法名均发生了变化,具体产生的变化相当于下面这段Java代码:

    // 相当于下面的Java代码
    public final class FooKt {
       public static final void foo1() {
          String var0 = "Hello, Jvm...";
          System.out.println(var0);
       }
    }
    

    可以看到第一个注解@file:JvmName("FooKt")的作用是使生成的类名变为FooKt,第二个注解的作用是使生成的方法名称变为foo1。

    注意:该注解不能改变类中生成的属性(成员变量)的名称。

    这里的注解中,我们看到了一个特殊的前缀@file:,这个注解前缀是Kotlin语言特有的一种标识,其作用是标记该注解最终会作用在生成的字节码的具体位置(属性、setter、getter等)

    2.@JvmMultifileClass

    说完了上面这个注解,就不得不提到@JvmMultifileClass这个注解,这个注解通常是与@JvmName结合使用的。其使用场景比较单一,看下面的例子:

    新建文件Util1.kt,添加如下代码:

    @file:JvmName("Utils")
    
    fun isEmpty(str: String?): Boolean {
        return null == str || str.length <= 0
    }
    

    新建文件Util2.kt,添加如下代码:

    @file:JvmName("Utils")
    
    fun isPhoneNumber(str: String): Boolean {
        return str.startsWith("1") && str.length == 11
    }
    

    编译以上代码,Kotlin编译器会提示错误Error:(1, 1) Kotlin: Duplicate JVM class name 'Utils' generated from: package-fragment, package-fragment,即生成的类名出现了重复。可是,如果我们就是希望声明使用多个文件,但方法生成到同一个类中呢?@JvmMultifileClass就是为解决这个问题而生的。

    我们在上面代码的基础上分别添加注解@JvmMultifileClass试试看:

    @file:JvmName("Utils")
    @file:JvmMultifileClass
    
    fun isEmpty(str: String?): Boolean {
        return null == str || str.length <= 0
    }
    
    @file:JvmName("Utils")
    @file:JvmMultifileClass
    
    fun isPhoneNumber(str: String): Boolean {
        return str.startsWith("1") && str.length == 11
    }
    

    添加注解@JvmMultifileClass之后,报错消失了,反编译生成的字节码,我们发生两个不同文件中的方法合并到了同一个类Utils中:

    // 生成的代码相当于下面这段Java代码
    public final class Utils {
       public static final boolean isEmpty(@Nullable String str) {
          return Utils__A1Kt.isEmpty(str);
       }
    
       public static final boolean isPhoneNumber(@NotNull String str) {
          return Utils__A2Kt.isPhoneNumber(str);
       }
    }
    

    这个注解在处理多个文件声明,合并到一个类的场景中发挥着举足轻重的作用。如果你有这样的需求,一定要谨记这个注解。

    3.@JvmOverloads

    由于Kotlin语言支持方法参数默认值,而实现类似功能Java需要使用方法重载来实现,这个注解就是为解决这个问题而生的,添加这个注解会自动生成重载方法。我们来试一下:

    @JvmOverloads
    fun foo(x: Int, y: Int = 0, z: Int = 0): Int {
        return x + y + z
    }
    
    // 生成的代码相当于下面这段Java代码
    public static final int foo(int x, int y, int z) {
      return x + y + z;
    }
       
    public static final int foo(int x, int y) {
      return foo(x, y, 0);
    }
    
    public static final int foo(int x) {
      return foo(x, 0, 0);
    }
    

    由此可见,通过这个注解可以影响带有参数默认值方法的生成,添加该注解将自动生成带有默认值参数数量的重载方法。这是一个非常有用的特性,方便Java端可以更高效地调用Kotlin端代码。

    4.@Throws

    由于Kotlin语言不支持CE(Checked Exception),所谓CE,即方法可能抛出的异常是已知的。Java语言通过throws关键字在方法上声明CE。为了兼容这种写法,Kotlin语言新增了@Throws注解,该注解的接收一个可变参数,参数类型是多个异常的KClass实例。Kotlin编译器通过读取注解参数,在生成的字节码中自动添加CE声明。

    为了便于理解,看一个简单的例子:

    @Throws(IllegalArgumentException::class)
    fun div(x: Int, y: Int): Float {
        return x.toFloat() / y
    }
    
    // 生成的代码相当于下面这段Java代码
    public static final float div(int x, int y) throws IllegalArgumentException {
          return (float)x / (float)y;
    }
    

    可以看到,添加了@Throws(IllegalArgumentException::class)注解后,在生成的方法签名上自动添加了可能抛出的异常声明(throws IllegalArgumentException),即CE。

    5.@Synchronized

    这个注解很容易理解,顾名思义,主要用于产生同步方法。Kotlin语言不支持synchronized关键字,处理类似Java语言的并发问题,Kotlin语言建议使用同步方法进行处理。
    但为了兼容Java,Kotlin语言支持使用该注解让编译器自动生成同步方法:

    @Synchronized
    fun start() {
        println("Start do something...")
    }
    
    // 生成的代码相当于下面这段Java代码
    public static final synchronized void start() {
      String var0 = "Start do something...";
      System.out.println(var0);
    }
    

    6.@JvmWildcard

    这个注解主要用于处理泛型参数,这涉及到两个新的知识点:逆变与协变。由于Java语言不支持协变,为了保证安全地相互调用,可以通过在泛型参数声明的位置添加该注解使用Kotlin编译器生成通配符形式的泛型参数(?extends ...)。

    看下面这段代码:

    class Box<out T>(val value: T)
    
    interface Base
    class Derived : Base
    
    fun boxDerived(value: Derived): Box<Derived> = Box(value)
    fun unboxBase(box: Box<Base>): Base = box.value
    

    按照正常思维,下面的两个方法转换到Java代码应该是这样:

    Box<Derived> boxDerived(Derived value) { …… }
    Base unboxBase(Box<Base> box) { …… }
    

    但问题是,Kotlin泛型支持型变,在Kotlin中,我们可以这样写unboxBase(Box(Derived())),而在Java语言中,泛型参数类型是不可变的,按照上面的写法显然已经做不到了。

    正确转换到Java代码应该是这样:

    Base unboxBase(Box<? extends Base> box) { …… }
    

    为了使这样的转换正确生成,我们需要在泛型参数的位置添加上面的注解:

    fun unboxBase(box: Box<@JvmWildcard Base>): Base = box.value
    

    7.@JvmSuppressWildcards

    这个注解的作用与@JvmWildcard恰恰相反,它是用来抑制通配符泛型参数的生成,即在不需要型变泛型参数的情况下,我们可以通过添加这个注解来避免生成型变泛型参数。

    fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
    
    // 生成的代码相当于下面这段Java代码
    Base unboxBase(Box<Base> box) { …… }
    

    正确使用上述注解,可以抹平Kotlin与Java泛型处理的差异,避免出现安全转换问题。

    8.@Volatile @Transient

    这两个注解恰好对应Java端的两个关键字volatiletransient,前者主要用于解决多线程脏数据问题,后者用于标记序列化对象中不参与序列化的属性。

    这两个注解比较简单,就不举例说明了。在遇到类似需要与Java互通的场景时,只需要将其关键字替换为该注解即可。

    相关文章

      网友评论

        本文标题:Kotlin常用注解

        本文链接:https://www.haomeiwen.com/subject/fxenrrtx.html