Kotlin 中具有两个类型参数的泛型 class

Generics class in Kotlin with two type parameters

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

有什么想法吗?我该如何实现?

这是因为重载冲突。

实际上,使用您当前的参数,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<Int, Int> 作为一个实例。

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


没有办法直接禁止 T == U,而不传递 Class<T>Class<U> 并检查它们。不幸的是,编译器不理解这一点,即使使用 require 或其他合约函数也是如此。此外,在您尝试之前,使用 : Any 不起作用。那是默认的界限。请记住,在 Java 中,一切都是 Object,在 Kotlin 中,一切都是 Any


您可以使用@JvmName(在's answer), but you'd use two different method names if you interop with Java. It might be slightly easier if you only use Kotlin though. Java-Kotlin interop has a bunch of @Jvm* annotations, most/all of which are covered in the docs中提到)解决这个问题。

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

在给定未知泛型类型的情况下,这些方法可以有效地具有相同的签名。因此,所呈现的基本情况对于 JVM 而言是不明确的。所以你只需要给它们一个替代名称,JVM(和 Java 或其他 JVM 语言)可以从中查看它们。您可以在 一个 或两者上使用 @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!

Error:(y, x) Kotlin: Overload resolution ambiguity:

@JvmName public final operator fun invoke(arg: String): MapBuilder defined in MapBuilder

@JvmName public final operator fun invoke(arg: String): MapBuilder defined in MapBuilder

如果您可以以一种它们可能不会重叠并且相同的方式定义您的泛型,您也可以解决这个问题 class。您 可能 根据所选的实际通用参数得到错误,但至少您的基本声明是允许的。这在 .

中有更详细的描述