【问题标题】:Kotlin generics fail when deduced from lambda argument从 lambda 参数推导出 Kotlin 泛型失败
【发布时间】:2019-09-03 21:23:02
【问题描述】:

今天我在创建一些 Kotlin 泛型时遇到了奇怪的行为。下面的代码说明了这个问题。

MCVE

class Generic<T>(private val initial: T? = null, private val source: (T)->Unit) {
    fun test(value: T) = source(value)
}

fun <T> createGeneric(initial: T? = null, source: (T)->Unit) = Generic(initial, source)

fun test() {
    val generic = createGeneric(null) { arg: Int -> println(arg) }
    generic.test(42) 
}

generic.test(42) 行产生错误:

整型文字不符合预期的类型 Nothing

问题:我通过{ arg: Int -&gt; println(arg) } lambda,即(Int)-&gt;Unit,如何将T推导出为Nothing?我仔细检查了变量generic的类型,推断为Generic&lt;Nothing&gt;

另外,如果类型被推断为Nothing,为什么它允许将(Int)-&gt;Unit类型的lambda作为第二个参数传递?

Kotlin Playground Example

【问题讨论】:

  • 我可以通过明确声明 createGeneric 返回 Generic&lt;T&gt; 来实现此功能

标签: generics kotlin lambda


【解决方案1】:

因为函数是contravariant in their argument,所以(Int) -&gt; Unit(Nothing) -&gt; Unit 的子类型。因此,T=Nothing 可以接受这两个论点,这就是推断的结果。

是的,T=Int 也可以接受它们,但 Kotlin 选择更具体的类型。

【讨论】:

  • 在我看来这很糟糕。在这种情况下,编译器不应该以不同的方式处理任何事情吗?老实说,这听起来像是语言陷阱。
  • 我同意这看起来很合理。我建议在discuss.kotlinlang.org 上提出这个问题。
【解决方案2】:

这是因为你的 lambda 永远不会执行,因为函数调用 source(value) 永远不会发生。

在 Kotlin 中,每当您有一个未完成的函数时,该函数都会返回一个类型 Nothing

如果您要在 Generic&lt;Int&gt; 中定义 Generic 采用的类型,那么您将摆脱该错误。但是因为您不这样做,编译器会尝试根据您在createGeneric(null) 中传递的值来推断它。因为你传入了null

当您在参数类型上传递 null 时,它会解析为 Nothing?,而当您将它传递到可空参数类型时,它会解析为 NothingNothing 在 Kotlin 中,是 absence 类型。因为null 本身没有类型,所以你得到Nothing

由于在整个 Generic 类的上下文中,您的 T 现在是 Nothing,因此您的测试函数签名现在是 test(value: Nothing)。这就是为什么当您尝试将 Int 传递给 test 函数时会出现错误的原因。

关于你的第二点“为什么我可以传入 (Int) -> Unit”?

val generic = createGeneric { arg: Int -> println(arg) }

如果你要去掉 null 参数,你会看到你得到的下一个错误将是因为你正在传递 (Int) -&gt; Unit 并且它不能正确推断出该参数类型为真.

【讨论】:

    猜你喜欢
    • 2018-05-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-21
    • 2020-12-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多