【问题标题】:Kotlin method overloadingKotlin 方法重载
【发布时间】:2018-08-25 19:31:15
【问题描述】:

以下声明在 Kotlin 中是合法的。

fun foo(): String = "foo_1"
fun <T> foo(): T = "foo_2" as T

作为我们得到的字节码:

public final static foo()Ljava/lang/String;

// signature <T:Ljava/lang/Object;>()TT;
// declaration: T foo<T>()
public final static foo()Ljava/lang/Object;

也可以从 Kotlin 调用这两种方法。

当我试图从 Java 中调用它们时,问题就来了:

ClassKt.foo()

模棱两可的电话。两种方法都匹配...

如何避免这样的问题?如何处理这样的方法?如果 3-rd 方 kt 库有同样的问题怎么办?

上面的例子是合成的。

【问题讨论】:

  • 最后一部分很简单:当第 3 方库这样做时,您可以写出缺陷并要求他们解决问题 :-).. 除此之外,可能的答案是:不要然后像这样重载你的方法。

标签: java kotlin overloading java-interop


【解决方案1】:

为什么它首先与 Kotlin 一起工作...在 Java 中有两种方法,例如:

private static String test() {
    return "";
}

private static <T> T test() {
    return null;
}

会导致编译时错误。对于 java 开发人员来说,这很明显,这些方法将具有相同的类型擦除。 但是这是由javac 强加的规则,不是由运行此代码的JVM 强加的。因此javac 不会将两种方法视为具有 only 不同的返回类型作为重载。好吧,kotlin 是一种不同的语言,因为它在JVM 上运行(需要有效的字节码),它允许将具有 only 的返回类型不同的方法视为重载。我还没有查看字节码并了解这是如何发生的;似乎这也仅适用于通用代码,因此在 kotlin 的情况下类型擦除可能略有不同。

现在,从 java 调用这种方法失败的原因应该很明显了。 Kotlin 为此提供了一个简洁的解决方案:@JvmName("someDistinctName")。我也不完全确定这在幕后是如何工作的......但是,尽管我认为这将创建一个桥接方法。

编辑

@JvmName 将在字节码级别重命名方法。

【讨论】:

  • 是的,这是有道理的。我了解语言级别和 vm 字节码级别有不同的“限制”。但仍然 - 在这种情况下 100% 互操作在哪里?
  • 我是从一个更复杂的例子开始的。如果我们将 suspend 修饰符添加到两个 kotlin 方法 - 字节码将变得更加有趣。
  • 我给了一个answer regarding 'How does erasure work in Kotlin',如果您有兴趣,它也会突出显示其中的一些内容(以及一些链接如何在 Java 中使用类似的东西)。还有其他一些东西不是 100% 兼容的,但反过来,例如尝试implement an annotation in Kotlin (in Java it works, in Kotlin it doesn't (yet?))
  • @SergeyRybalkin 当你说在这种情况下 100% 互操作在哪里? 时,我不完全确定我是否关注你。你到底是什么意思?
【解决方案2】:

你可以使用@JvmName来区分java调用时的代码:

@JvmName("fooString")
fun foo(): String = "foo_1"

fun <T> foo(): T = "foo_2" as T

这将允许在 Java 中使用 ClassKt.fooString() 调用 String 方法,从而解决冲突。

【讨论】:

    【解决方案3】:

    一个简单的解决方案是在 Kotlin 中编写一个辅助方法并调用它。


    仅使用 Java 的另一种方法是为这两种方法获取 MethodHandle 并使用它们:

    MethodHandle MH_fooString = lookup().findStatic(ClassKt.class, "foo", methodType(String.class));
    MethodHandle MH_fooT = lookup().findStatic(ClassKt.class, "foo", methodType(Object.class));
    
    String foo = (String) MH_fooString.invokeExact();
    

    这并不简单,但需要处理异常。

    【讨论】:

    • 并且会比通常的方法调用慢
    • @Eugene 如果您将 MethodHandles 存储在静态最终字段中,我认为不会有任何显着的性能差异。优化应该不难。
    • 这几乎是一个反射调用,它存储在哪里并不重要,与普通的方法调用相比,它应该是有意义的。
    • @Eugene 这与反射调用完全不同,因为该方法是静态已知的并且可以由 JIT 优化。通过足够的热身,在我做的一个简单测试中,我只能看到两者之间的差异
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多