【问题标题】:Generics class in Kotlin with two type parametersKotlin 中具有两个类型参数的泛型类
【发布时间】:2018-11-16 18:06:24
【问题描述】:
class MapBuilder<T,U> {
    operator fun invoke(arg: T): MapBuilder<T, U> {
        return this
    }
    operator fun invoke(arg: U): MapBuilder<T, U> {
        return this
    }
}

当然,由于 JVM 限制,它不起作用。

Platform declaration clash: The following declarations have the same JVM signature (invoke(Ljava/lang/Object;)Lcom/test/tests/MapBuilder;):
    operator fun invoke(arg: T): MapBuilder<T, U> defined in com.test.tests.MapBuilder
    operator fun invoke(arg: U): MapBuilder<T, U> defined in com.test.tests.MapBuilder

任何想法,我该如何实现?

【问题讨论】:

  • Ofc 这是不可能的,因为如果我声明 MapBuilder&lt;String, String&gt; 则无法区分调用哪个方法。您解释一下您要达到什么目的以获得更好的答案会很有帮助。

标签: java generics kotlin legacy


【解决方案1】:

给定未知的泛型类型,这些方法可以有效地具有相同的签名。因此,所呈现的基本情况对 JVM 来说是模棱两可的。因此,您只需给它们一个替代名称,JVM(以及 Java 或其他 JVM 语言)将从中查看它们。您可以在 one 或两者上使用 @JvmName 注释来为它们提供内部名称。这不会影响 Kotlin 以及您在 Kotlin 代码中使用的名称,它们会像以前一样看到它们。

class MapBuilder<T,U> {
    @JvmName("invokeWithT")
    operator fun invoke(arg: T): MapBuilder<T, U> {
        return this
    }

    @JvmName("InvokeWithU") // technically don't need both of these
    operator fun invoke(arg: U): MapBuilder<T, U> {
        return this
    }
}

现在你很好,可以独立使用它们了。

val builder = MapBuilder<String, Integer>()
builder("hi") // success!
builder(123)  // success!

请注意,如果 TU 不明确,则在尝试调用它们时可能会出现其他错误。

val builder = MapBuilder<String, String>()
builder("hi") // error!

错误:(y, x) Kotlin:重载分辨率歧义:

@JvmName public final operator fun invoke(arg: String): MapBuilder中定义的MapBuilder

@JvmName public final operator fun invoke(arg: String): MapBuilder中定义的MapBuilder

如果您可以以一种可能不会重叠并且属于同一个类的方式定义您的泛型,那么您也可以解决这个问题。您可能会收到错误,具体取决于所选的实际泛型参数,但至少允许您的基本声明。这在Zoe's answer中有更详细的描述。

【讨论】:

    【解决方案2】:

    这是因为重载冲突。

    实际上,使用您当前的参数,T 可以等于 U。如果您熟悉重载,您应该知道这是不允许的:

    fun something(x: Int){ /* foo */ }
    fun something(x: Int){ /* bar */ }
    

    但举个例子:

    fun something(x: Int){ /* foo */ }
    fun something(x: Float){ /* bar */ }
    

    因为它们可能相同,这会导致冲突。它怎么知道调用哪个方法?

    使用全范围,编译器会报错。如果您在一个参数上使用: SomeClass,它将停止抱怨。但这是一个随机的例子:

    class MapBuilder<T, U : Logger> {
        operator fun invoke(arg: T): MapBuilder<T, U> {
            return this
        }
        operator fun invoke(arg: U): MapBuilder<T, U> {
            return this
        }
    }
    
    fun t(){
        MapBuilder<Logger, Logger>().invoke(LoggerFactory.getLogger(""))
    }
    

    invoke 会模棱两可。现在只有当你有两个相等的类型时才存在问题;它使用哪个?

    现在,您的 MCVE 非常小。我不知道你用 T 和 U 做什么。结果,我不能给你任何代码示例。但以下是您需要知道的:

    您不能将其中两个方法用于任何类型,因为它们可能会发生冲突。如果使用两个相等的类型,即使使用方差也会导致重载问题。因此,它会排除 MapBuilder&lt;Int, Int&gt; 的一个实例。

    您可以使用单个方法,也可以将它们拆分为两个不同命名的方法。这个名字表明它是一个建造者,所以你可以有withKey(T t)withValue(U u)


    如果不通过 Class&lt;T&gt;Class&lt;U&gt; 并检查它们,就无法直接禁止 T == U。不幸的是,即使使用 require 或其他合约函数,编译器也不理解这一点。此外,在您尝试之前,使用 : Any 不起作用。这是默认的界限。请记住,Java 中的一切都是Object,Kotlin 中的一切都是Any


    您可以使用@JvmName 解决此问题(在Jayson Minard 的回答中提到),但如果您与Java 互操作,您将使用两个不同的方法名称。不过,如果您只使用 Kotlin,可能会稍微容易一些。 Java-Kotlin 互操作有一堆 @Jvm* 注释,其中大部分/全部都包含在 in the docs 中。

    即使使用@JvmName,它仍然允许&lt;String, String&gt;,直到调用冲突的方法。如果你想断言 T != U 无论如何,你需要运行类检查。

    【讨论】:

    • 有什么方法可以限制 T 和 U 的类型以使其不同?禁止 , case?!
    • @DmitrySokolov 当你有方法时不会。刚刚编辑了答案。
    • "你不能有任何类型的两个方法,因为它们可能会发生冲突。"您的 : Logger 示例清楚地表明这不是原因,因为它是允许的,但方法仍然可能发生冲突。没有规则禁止可能有模糊调用的重载方法。
    • @AlexeyRomanov 是的,但在第一种情况下,它们都有相同的界限。实际上类似于fun invoke(a: Any)fun invoke(a: Logger)。显然,不同的类型有不同的实际输出,但它仍然没有以相同的方式匹配的类型,这取决于泛型类型。我想指出我说的是有效。我的意思不是它编译成的样子,也不是它的确切工作原理。
    • 你说得对,我现在在我的结尾提到了这一点,指着你的。
    猜你喜欢
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-04
    • 2018-05-15
    • 1970-01-01
    • 2018-04-16
    相关资源
    最近更新 更多