当前位置: 首页 > 知识库问答 >
问题:

擦除在Kotlin中是如何工作的?

程仲卿
2023-03-14

在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 是否将方法的返回类型视为其签名的一部分?此外,为什么 Kotlin 中的方法签名与 Java 相比,它遵循完整的参数类型?

共有2个答案

西门振
2023-03-14

虽然@Streloks的答案是正确的,但我想更深入地研究它为什么有效。

第一个变体之所以有效,是因为它在Java Byte代码中不被禁止。虽然Java编译器抱怨它,即Java语言规范不允许它,但Byte代码确实如此,正如 https://community.oracle.com/docs/DOC-983207 和 https://www.infoq.com/articles/Java-Bytecode-Bending-the-Rules 中记录的那样。在 Byte 代码中,每个方法调用都引用方法的实际返回类型,这在编写代码时并非如此。

不幸的是,我找不到实际的来源,为什么会这样。

关于Kotlins名称解析的文档包含一些有趣的观点,但我没有在那里看到您的实际案例。

真正帮助我理解它的是@Yole对Kotlin类型擦除的回答——为什么只有泛型类型不同的函数是可编译的,而只有返回类型不同的函数是不可编译的?更准确地说,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变得必要。这样您实际上可以确保字节码中没有名称冲突。

邵刚洁
2023-03-14

实际上,Kotlin 知道示例中这两种方法之间的区别,但 jvm 不会。这就是为什么这是一个“平台”冲突。

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

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

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

这个注释就是因为这个原因而存在的。您可以在互操作文档中阅读更多内容。

 类似资料:
  • 正如在javadoc提到的, 如果类型参数是无界的,则用其边界或对象替换泛型类型中的所有类型参数。因此,生成的字节码只包含普通类、接口和方法。 如果需要,插入类型转换以保持类型安全。 我理解以上两点,使用javac处理类型参数,如T和

  • 问题内容: 我们正在运行Jenkins 2.x,并且喜欢新的Pipeline插件。但是,由于存储库中有如此多的分支,因此磁盘空间会迅速填满。 是否有任何与Pipeline兼容的插件,我可以在成功构建后清除工作空间? 问题答案: 您可以将其用作管道Jenkinsfile的最后一步(假设您没有更改工作目录)。

  • 因此,我需要开始考虑关联,最好是利用我对并发工具的现有知识来加快进程。 我试着为他们跟踪Google codelab,虽然它给了我一点理解,但它也提出了许多未回答的问题,所以我试图通过编写一些代码、调试和查看日志输出来弄脏我的手。 根据我的理解,一个coroutine由两个主要构件组成;挂起函数是您执行工作的地方,而coroutine上下文是您执行挂起函数的地方,这样您就可以对coroutine将

  • Kotlin定义了自己的类: 该类的实例是通过中定义的内联函数构造的: null 我认为在编译之前必须进行某种代码预处理。 请随意用一个更贴切的标题编辑这个问题。

  • 我试图理解关键字的用途,显然它允许我们对泛型进行反思。 然而,当我把它排除在外时,它的工作也一样好。有人想解释一下这什么时候会有实际的不同吗?

  • 在Spark中是如何工作的? 如果我们注册一个对象作为一个表,会将所有数据保存在内存中吗?