擦除如何在Kotlin工作?

时间:2017-03-21 01:19:49

标签: kotlin

在Kotlin中,以下代码编译:

class Foo {
    fun bar(foo: List<String>): String {
        return ""
    }

    fun bar(foo: List<Int>): Int {
        return 2;
    }
}

但是,此代码不会:

class Foo {
    fun bar(foo: List<String>): String {
        return ""
    }

    fun bar(foo: List<Int>): String {
        return "2";
    }
}

编译它会导致以下错误:

Error:(8, 5) Kotlin: Platform declaration clash: The following declarations have the same JVM signature (foo(Ljava/util/List;)Ljava/lang/String;):
    fun foo(layout: List<Int>): String
    fun foo(layout: List<String>): String

在Java中,两个示例都不会编译:

class Foo {
    String bar(List<Integer> foo) {
        return "";
    }

    Integer bar(List<String> foo) {
        return 2;
    }
}

class Foo {
    String bar(List<Integer> foo) {
        return "";
    }

    String bar(List<String> foo) {
        return "2";
    }
}

不出所料,两个先前的代码段都会产生熟悉的编译器错误:

Error:(13, 12) java: name clash: bar(java.util.List<java.lang.String>) and bar(java.util.List<java.lang.Integer>) have the same erasure

让我感到惊讶的是,第一个Kotlin示例完全起作用,其次,如果它有效,为什么第二个Kotlin示例会失败? Kotlin是否将方法的返回类型视为其签名的一部分?此外,与Java相比,为什么Kotlin中的方法签名会尊重完整的参数类型?

2 个答案:

答案 0 :(得分:19)

实际上Kotlin知道你的例子中两种方法之间的区别,但jvm不会。这就是为什么它是一个&#34;平台&#34;冲突。

您可以使用@JvmName注释来编译第二个示例:

class Foo {
  @JvmName("barString") fun bar(foo: List<String>): String {
    return ""
  }

  @JvmName("barInt") fun bar(foo: List<Int>): String {
    return "2";
  }
}

这个注释就是出于这个原因而存在的。您可以在interop documentation中阅读更多内容。

答案 1 :(得分:3)

虽然@Streloks答案是正确的,但我想更深入地了解它的工作原理。

第一个变体起作用的原因是,Java Byte代码中没有禁止使用它。 Java编译器抱怨Java language specification does not allow it,而Byte代码却抱怨,https://community.oracle.com/docs/DOC-983207https://www.infoq.com/articles/Java-Bytecode-Bending-the-Rules中也有记录。在字节码中,每个方法调用均指该方法的实际返回类型,而编写代码时并非如此。

不幸的是,我找不到真正的来源,为什么是这样。

关于Kotlins name resolution的文档包含一些有趣的观点,但我在那里没有看到您的实际情况。

真正使我理解的是从answer from @YoleKotlin type erasure - why are functions differing only in generic type compilable while those only differing in return type are not?,更确切地说,kotlin编译器在决定调用哪种方法时不会考虑变量的类型。

因此,这是一个经过深思熟虑的设计决策,即在变量上指定类型将不会影响哪个方法被调用,而相反,即被调用的方法(带有或不带有通用信息)会影响类型。要使用的。

然后将规则应用于以下示例很有意义:

fun bar(foo: List<String>) = ""    (1)
fun bar(foo: List<Int>) = 2        (2)

val x = bar(listOf("")) --> uses (1), type of x becomes String
val y = bar(listOf(2))  --> uses (2), type of y becomes Int

或者有一种提供通用类型但不使用通用类型的方法:

fun bar(foo: List<*>) = ""         (3)
fun <T> bar(foo: List<*>) = 2      (4)

val x = bar(listOf(null))          --> uses (3) as no generic type was specified when calling the method, type of x becomes String
val y = bar<String>(listOf(null))  --> uses (4) as the generic type was specified, type of y becomes Int

这也是以下原因不起作用的原因:

fun bar(foo: List<*>) = ""
fun bar(foo: List<*>) = 2

这是不可编译的,因为它会导致重载冲突,因为在尝试识别要调用的方法时未考虑分配变量本身的类型:

val x : String = bar(listOf(null)) // ambiguous, type of x is not relevant

关于该名称的冲突:现在,只要您使用相同的名称,相同的返回类型和相同的参数(擦除了通用类型),您实际上将在字节码中获得完全相同的方法签名。这就是为什么@JvmName变得必要的原因。这样,您实际上可以确保字节码中没有名称冲突。