将泛型类型修复为第一个参数的类型

时间:2016-07-17 16:41:16

标签: kotlin

我想编写一个扩展函数,该函数可用于任何类型并接受相同类型或子类型的参数,但不是完全不同的类型。

我尝试过天真的方法,但它没有工作:

fun <T> T.f(x: T) {
}

fun main(args: Array<String>) {
  "1".f("1") // ok
  "1".f(1) // should be error
}

似乎编译器只使用Any for T.我希望T固定为接收器类型。

4 个答案:

答案 0 :(得分:4)

唯一的方法是告诉编译器你想要什么。

fun <T> T.f(x: T) {
}

为了使用它,你必须告诉Kotlin你想要的类型。

"1".f<String>("2")  // Okay
"1".f(2)  // Okay (see voddan's answer for a good explanation)
"1".f<String>(2)  // Fails because 2 isn't a String
"1".f<Int>(2)  // Fails because "1" isn't an Int

答案 1 :(得分:3)

当你像fun <T> T.f(x: T) {}一样调用"1".f(1)时,编译器会查找StringInt的常见超类型,即Any。然后它决定T is Any,并且不发出错误。影响此流程的唯一方法是明确指定T"1".f<String>(1)

由于所有检查都是由编译器执行的,因此该问题与类型擦除无关。

答案 2 :(得分:3)

你的问题就像说&#34; 约翰比卡尔年长3岁,卡尔比约翰小3岁&#34; ......如果没有更多信息,你仍然不知道他们的年龄。这是您给编译器提供的证据类型,然后您希望它能够正确猜测。你可以从这些信息中得到的唯一真相是,约翰至少3岁,卡尔至少1天。

这种类型的假设就像编译器找到Any的共同上限一样。它有两种强大的文字类型可供选择,也无法改变。它将如何决定IntString是否更重要,同时您告诉它T的上限为Any?是有效的,因为您的类型规格。因此,安全的答案是看两个文字是否符合T: Any?的标准,当然它们都是,它们都有Any的祖先。编译器符合您的所有条件,即使您不想要它。

如果你有打破平局的标准,这将有所不同。例如,如果返回类型为T且类型为String的变量接收该值,则会影响类型推断的决策。例如,这会产生错误:

fun <T: Any> T.f2(x: T): T = x

val something: String = "1".f2(1) // ERROR

因为现在类型T由&#34;左侧&#34;锚定。期待String毫无疑问的表达。

这也可能是一个非预期的类型推断问题,检查YouTrack中报告的问题或添加自己的问题以从编译器团队获得明确的答案。我添加了一个功能请求KT-13138,用于锚定特定类型参数以查看团队如何响应。

答案 3 :(得分:1)

您可以通过使Textension property返回一个可调用对象来将f修复为接收器类型:

val <T> T.f: (T) -> Unit
    get() = { x -> }

fun main(vararg args: String) {
    "1".f("1") // will be OK once KT-10364 is resolved
    "1".f(1) // error: The integer literal does not conform to the expected type String
}

不幸的是"1".f("1")目前导致错误:&#34;类型不匹配:推断类型是字符串但是T是预期的&#34;。这是编译器问题。见KT-10364。另见KT-13139。您可以投票和/或观看更新问题。在修复此问题之前,您仍然可以执行以下操作:

"1".f.invoke("1")
/* or */
("1".f)("1")
/* or */
val f = "1".f
f("1")