Kotlin的变量重名问题

问题背景

以下是一个简化的问题,可以思考一下,以下代码会出现什么问题

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Student {
fun getName(): String
}

class Main : Student {
private val name by lazy { "name" }
override fun getName() = name
}

//测试,例如我要这样调用
fun main() {
println(Main().getName())
}

实际上,上面的代码在IDE中,并没有任何提示和报错信息,只有在编译的时候才会出现如下错误,

1
2
3
Platform declaration clash: The following declarations have the same JVM signature (getName()Ljava/lang/String;):
fun `<get-name>`(): String defined in Main
fun getName(): String defined in Main

大意是说,2个方法名重复了,为什么呢?

解惑

Kotlin与Java不同的之一,就是Kotlin会默认给变量添加get和set方法,默认的规则也是变量名前面直接添加get或set,如果我们在显示的写了一个方法名相同的函数,就有可能出现方法签名冲突,所以上面的情况就出现了方法名的冲突。
如果我们去掉显示写的getName(),可以看到生成的函数

1
2
3
4
5
6
7
8
public final class Main {
private final Lazy name$delegate;
//这个 get函数是自动生成的
private final String getName() {
Lazy var1 = this.name$delegate;
return (String)var1.getValue();
}
}

解决办法

既然我们定位到了问题的原因,那我们重新给自己的写的函数命名一下,是不是就可以了?这种方法是可以的,但是不够优雅,其实Kotlin给我提供了一下方法,去处理这种情况。

测试方法一:@JvmName 指定生成的函数名

@JvmName 注解是 Kotlin 提供的一个用于指定在编译为 Java 字节码时生成的 Java 类或方法名称的注解。它允许你在 Kotlin 代码中为特定的元素指定一个不同于默认生成的 Java 名称。
但是,在上面的代码中,你会遇到错误。

1
This annotation is not applicable to target 'member property with delegate'

image.png
因为我们用了Lazy代理,我们需要用 @get:JvmName(“name”) 来处理这种情况。

测试方法二: @JvmField

@JvmField 是 Kotlin 提供的一个注解,用于将属性暴露为公共的 Java 字段,而不是生成 getter 和 setter 方法。是不是这样就可以了?其实也会遇到一个错误,就是这里使用了代理类,代理类实际上是生成了函数,通过函数中转的,java并不会有一个同名Field, 而是一个Lazy name$delegate;如果是普通的属性,这个注解是可以的。

1
JvmField cannot be applied to delegated property

这个问题的本质是Kotlin默认生成了get方法,那Kotlin就不能默认不生成get方法吗?其实对一个普通final feild是没有生成get和set,是直接访问变量的,自动生成get个set 常见的是var类型

方法三:可以添加前缀_

添加前缀的方法,本质是让,变量不同名,但是代码可读性没有改变,个人认为是比较优雅的一种方式。@get:JvmName(“name”) 显得添加的信息更多,更打扰阅读。