Kotlin 允许与具有不同返回类型的属性 getter 相同的函数签名

Kyl*_*ang 6 kotlin

更新 2020-12-23

起源描述有点混乱。Kotlin 不仅允许在子类中使用 getter 相同的签名,而且在自类中也允许使用相同的签名。所以这也是允许的:

open class BaseRequest {
    val params = mutableMapOf<String, String>()
    fun getParams(): List<String> {
        return params.values.toList()
    }
}
Run Code Online (Sandbox Code Playgroud)

正如@Slaw 所说,这是 kotlin 编译器的行为,因为 JVM 使用地址而不是“签名”调用正确的方法,所以它可以工作。


我遇到了一种情况,似乎 Kotlin 允许子类创建与超类的 getter 相同的签名。

通常,函数具有相同的签名并且不允许不同的返回类型。所以我对这种情况感到困惑。我不确定这是否是故意的。

这是一个示例:

open class BaseRequest {
    val params = mutableMapOf<String, String>()

    init {
        params["key1"] = "value1"
    }
}

class SpecificRequest : BaseRequest() {
    init {
        params["key2"] = "value2"
    }

    fun getParams(): List<String> {
        return params.values.toList()
    }
}
Run Code Online (Sandbox Code Playgroud)

MediatorRequest 有一个函数getParams(),它与它的超类具有相同的签名,但具有不同的返回类型。在使用这个函数时,子类和超类似乎有相同声明的不同实现。

fun main() {
    val specificRequest = SpecificRequest()
    println("specificRequest.params: ${specificRequest.params}")
    println("specificRequest.getParams(): ${specificRequest.getParams()}")
    println("(specificRequest as BaseRequest).params: ${(specificRequest as BaseRequest).params}")
}
Run Code Online (Sandbox Code Playgroud)

输出将是这样的:

specificRequest.params: {key1=value1, key2=value2}
specificRequest.getParams(): [value1, value2]
(specificRequest as BaseRequest).params: {key1=value1, key2=value2}
Run Code Online (Sandbox Code Playgroud)

如果我们查看反编译的Java代码,有两个方法具有相同的签名和不同的返回类型,这在Java中确实是不允许的。

specificRequest.params: {key1=value1, key2=value2}
specificRequest.getParams(): [value1, value2]
(specificRequest as BaseRequest).params: {key1=value1, key2=value2}
Run Code Online (Sandbox Code Playgroud)

我知道函数名称不合适,但存在潜在风险,如果我们在 .java 中使用 SpecificRequest,在将实例转换为它的超类之前,我们无法访问 Map 参数。这可能会导致误解。

Sla*_*law 5

Java语言和 JVM之间存在差异。Java 语言不允许在同一个类中声明两个名称相同但返回类型不同的方法。这是语言的限制。然而,JVM 完全有能力区分这两种方法。而且由于 Kotlin 是它自己的语言,因此它不必遵循与 Java 完全相同的规则——即使是针对 JVM(因此编译为字节码)。

考虑以下 Kotlin 类:

class Foo {
    val bar = mapOf<Any, Any>()
    fun getBar() = listOf<Any>()
}
Run Code Online (Sandbox Code Playgroud)

如果您编译该类,然后检查字节码,javap您将看到:

Compiled from "Foo.kt"
public final class Foo {
  public final java.util.Map<java.lang.Object, java.lang.Object> getBar();
  public final java.util.List<java.lang.Object> getBar();
  public Foo();
}
Run Code Online (Sandbox Code Playgroud)

所以这两个函数肯定存在,尽管名称相同。但是,如果您访问该属性并调用该函数,您将看到:

fun test() {
    val foo = Foo()
    val bar1 = foo.bar
    val bar2 = foo.getBar()
}
Run Code Online (Sandbox Code Playgroud)

变成:

fun test() {
    val foo = Foo()
    val bar1 = foo.bar
    val bar2 = foo.getBar()
}
Run Code Online (Sandbox Code Playgroud)

这表明字节码知道要调用哪个函数。JVM 可以处理这个问题。

但有一个警告。以下将无法编译:

 public static final void test();
    descriptor: ()V
    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=3, args_size=0
         0: new           #8                  // class Foo
         3: dup
         4: invokespecial #11                 // Method Foo."<init>":()V
         7: astore_0
         8: aload_0
         9: invokevirtual #15                 // Method Foo.getBar:()Ljava/util/Map;
        12: astore_1
        13: aload_0
        14: invokevirtual #18                 // Method Foo.getBar:()Ljava/util/List;
        17: astore_2
        18: return
Run Code Online (Sandbox Code Playgroud)

为什么?我并不乐观,但我相信这与语法有关。Kotlin 编译器总是可以根据您是否使用foo.bar或来轻松判断您打算调用哪个函数foo.getBar()。但是调用这两个getBaz()函数的语法是相同的,这意味着编译器无法轻松地分辨出您打算在所有情况下调用哪个函数(因此它不允许上述情况)。