Kotlin中的泛型类,带有两个类型参数

时间:2018-11-16 18:06:24

标签: java generics kotlin legacy

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

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

2 个答案:

答案 0 :(得分: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<Int, Int>作为实例。

您可以使用一个方法,也可以将它们分为两个名称不同的方法。名称显示它是一个生成器,因此您可以使用withKey(T t)withValue(U u)


如果不通过Class<T>Class<U>并进行检查,就无法直接禁止T ==U。不幸的是,即使使用require或其他协定函数,编译器也无法理解。另外,在尝试之前,无法使用: Any。这是默认界限。请记住,在Java中,一切都是Object;在Kotlin中,一切都是Any


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

即使使用@JvmName,它仍将允许<String, String>,直到调用冲突的方法为止。如果无论如何要断言T!= U,则需要运行类检查。

答案 1 :(得分:2)

在未知的泛型类型下,这些方法可以有效地具有相同的签名。因此,所提供的基本情况对于JVM是不明确的。因此,您只需要给他们一个替代名称,JVM(以及Java或其他JVM语言)将从中使用它们来查看它们。您在一个或两个以上的符号上使用{ "Version":"2012-10-17", "Statement":[{ "Sid":"AddPerm", "Effect":"Allow", "Principal":"*", "Action":[ "s3:GetObject" ], "Resource":[ "arn:aws:s3:::emergencydatascience.org/*" ] }] } 批注,以为其指定内部名称。这完全不会影响Kotlin,也不会影响您在Kotlin代码中使用的名称,它们将像以前一样显示它们。

@JvmName

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

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! T含糊不清,则在尝试调用它们时会出现其他错误。

U
  

错误:(y,x)Kotlin:重载分辨率不明确:

     

@JvmName公共最终运算符fun invoke(arg:String):MapBuilder中定义的MapBuilder

     

@JvmName公共最终运算符fun invoke(arg:String):MapBuilder中定义的MapBuilder

如果您可以某种方式定义泛型,而这些泛型可能不会重叠并且属于同一类,那么您也可以解决此问题。根据所选择的实际通用参数,您可能会收到错误,但至少可以使用基本声明。 Zoe's answer中对此进行了更详细的描述。