技术小黑屋

JvmName 注解在 Kotlin 中的应用

JvmName注解是Kotlin提供的一个可以变更编译器输出的注解,这里简单的介绍一下其使用规则。

应用在文件上

未应用@JvmName

1
2
3
4
5
6
7
8
package com.example.jvmannotationsample

import android.net.Uri


fun String.toUri(): Uri {
    return Uri.parse(this)
}

当我们在Java中调用上面的toUri方法时

1
StringExtKt.toUri("https://droidyue.com");

生成的 class 文件名称为

1
./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/StringExtKt.class

应用@JvmName

1
2
3
4
5
6
7
8
9
@file:JvmName("StringUtil")
package com.example.jvmannotationsample

import android.net.Uri


fun String.toUri(): Uri {
    return Uri.parse(this)
}

在Java中调用

1
StringUtil.toUri("https://droidyue.com");

生成的 class 文件名为

1
./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/StringUtil.class

作用在方法上

1
2
3
4
5
6
package com.example.jvmannotationsample.jvm_name

@JvmName("isOK")
fun String.isValid(): Boolean {
    return isNotEmpty()
}

生成的对应的class 文件,我们可以看到方法名称已经修改了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
javap -c ./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/jvm_name/OnMethodSampleKt.class
Compiled from "OnMethodSample.kt"
public final class com.example.jvmannotationsample.jvm_name.OnMethodSampleKt {
  public static final boolean isOK(java.lang.String);
    Code:
       0: aload_0
       1: ldc           #11                 // String $this$isValid
       3: invokestatic  #17                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
       6: aload_0
       7: checkcast     #19                 // class java/lang/CharSequence
      10: astore_1
      11: iconst_0
      12: istore_2
      13: aload_1
      14: invokeinterface #23,  1           // InterfaceMethod java/lang/CharSequence.length:()I
      19: ifle          26
      22: iconst_1
      23: goto          27
      26: iconst_0
      27: ireturn
}

所以,我们在Java代码中,可以这样调用

1
2
3
public static void testJvmNameOnMethod() {
    OnMethodSampleKt.isOK("");
}

但是,我们在Kotlin代码中,还是只能使用isValid而不是isOK

1
2
3
4
fun testJvmNameOnMethod() {
    "".isValid()
//    "".isOK() unresolved reference
}

那么问题就奇怪了,生成的class里面的方法是isOK,怎么还能调用isValid呢?

1
2
3
4
5
6
7
8
9
10
javap -c ./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/jvm_name/KotlinPlaygroundKt.class
Compiled from "KotlinPlayground.kt"
public final class com.example.jvmannotationsample.jvm_name.KotlinPlaygroundKt {
  public static final void testJvmNameOnMethod();
    Code:
       0: ldc           #8                  // String
       2: invokestatic  #14                 // Method com/example/jvmannotationsample/jvm_name/OnMethodSampleKt.isOK:(Ljava/lang/String;)Z
       5: pop
       6: return
}

是的,Kotlin编译器将isValid在字节码层面又替换成了isOK

关于@JvmName作用到方法上,比较好的例子(来自Kotlin官网)是这样的

1
2
3
4
5
6
7
fun List<String>.filterValid(): List<String> {
    TODO()
}

fun List<Int>.filterValid(): List<Int> {
    TODO()
}
1
2
3
~/JVMAnnotationSample/app/src/main/java/com/example/jvmannotationsample/jvm_name/GenericList.kt: (3, 1): Platform declaration clash: The following declarations have the same JVM signature (filterValid(Ljava/util/List;)Ljava/util/List;):
    fun List<Int>.filterValid(): List<Int> defined in com.example.jvmannotationsample.jvm_name in file GenericList.kt
    fun List<String>.filterValid(): List<String> defined in com.example.jvmannotationsample.jvm_name in file GenericList.kt

上面的两个方法声明会导致Kotlin编译出错,因为

由于JVM对于泛型采取了类型擦除,List<Int>.filterValid()List<String>.filterValid()实际上对应的都是List.filterValid()

所以,对应的解决方法

  • 修改两个的方法名称,比如List<String>.filterValid()修改成List<String>.filterValidString()
  • 第二种就是使用@JvmName达到第一种方法的效果

具体修改如下所示

1
2
3
4
5
6
7
8
9
10
11
package com.example.jvmannotationsample.jvm_name

@JvmName("filterValidString")
fun List<String>.filterValid(): List<String> {
    TODO()
}

@JvmName("filterValidInt")
fun List<Int>.filterValid(): List<Int> {
    TODO()
}

作用在属性上

除此之外,@JvmName还可以作用在属性上。比如

1
2
3
4
5
package com.example.jvmannotationsample.jvm_name

@get:JvmName("x")
@set:JvmName("changeX")
var x: Int = 23

在Java中对应的调用

1
2
3
4
public static void testJvmNameOnProperty() {
        OnPropertiesSampleKt.changeX(111);
        OnPropertiesSampleKt.x();
    }

在Kotlin中对应的调用

1
2
3
4
fun testJvmNameOnProperty() {
    x = 1111
    x
}

和作用在方法上一样,其实现原理一致,具体如下面的反编译代码可见一斑。

Java调用处的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
javap -c ./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/jvm_name/OnPropertiesSampleKt.class
Compiled from "OnPropertiesSample.kt"
public final class com.example.jvmannotationsample.jvm_name.OnPropertiesSampleKt {
  public static final int x();
    Code:
       0: getstatic     #11                 // Field x:I
       3: ireturn

  public static final void changeX(int);
    Code:
       0: iload_0
       1: putstatic     #11                 // Field x:I
       4: return

  static {};
    Code:
       0: bipush        23
       2: putstatic     #11                 // Field x:I
       5: return
}

Kotlin调用处的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
javap -c ./app/build/tmp/kotlin-classes/debug/com/example/jvmannotationsample/jvm_name/KotlinPlaygroundKt.class
Compiled from "KotlinPlayground.kt"
public final class com.example.jvmannotationsample.jvm_name.KotlinPlaygroundKt {


  public static final void testJvmNameOnProperty();
    Code:
       0: sipush        1111
       3: invokestatic  #36                 // Method com/example/jvmannotationsample/jvm_name/OnPropertiesSampleKt.changeX:(I)V
       6: invokestatic  #40                 // Method com/example/jvmannotationsample/jvm_name/OnPropertiesSampleKt.x:()I
       9: pop
      10: return
}

相关文章