【问题标题】:Kotlin force nullable generic type into a non-nullable of the same generic type?Kotlin 强制可空泛型类型为同一泛型类型的不可空类型?
【发布时间】:2019-01-18 08:13:14
【问题描述】:

首先我只想指出我知道Force a null into non-nullable typeKotlin Generics and nullable Class type 但我认为这些问题与我的不同(如果我错了请纠正我)。

背景

我正在开发一个名为Awaitility 的库,简单地说,它旨在等待谓词评估为真。 Kotlin API 提供了一种编写表达式的方法,如下所示:

// Create a simple data class example
data class Data(var value: String)
// A fake repository that returns a possibly nullable instance of Data
interface DataRepository {
    // Invoked from other thread
    fun loadData() : Data?
} 
val dataRepository = .. // Implementation of DataRepository

// Now Awaitility allows you to wait until the "value" in Data is equal to "Something"
val data : Data = await untilCallTo { dataRepository.loadData() } has {
    value == "Something"
}

这是因为如果dataRepository.loadData() 返回nullhas 返回false,并且如果datanull 则从不调用提供的接收器函数({ value == "Something" })。如果条件不满足,Awaitility 也会抛出异常,因此我们知道从表达式返回的内容类型为 Data(而不是 Data?),如示例中所示。 p>

has 函数是这样实现的:

infix fun <T> AwaitilityKtUntilFunCondition<T?>.has(pred: T.() -> Boolean) = factory.until(fn) { t: T? ->
    if (t == null) {
        false
    } else {
        pred(t)
    }
} as T

AwaitilityKtUntilFunCondition 看起来像这样:

data class AwaitilityKtUntilFunCondition<T> internal constructor(internal val factory: ConditionFactory, internal val fn: () -> T?)

(如果需要,您也可以找到 ConditionFactory here

虽然上面的示例在传递给untilCallTo 的 lambda 返回可空类型(Data?)时效果很好,但如果我们将其传递给不可空类型(即Data),则无法编译。例如,如果我们简单地将存储库修改为如下所示:

interface DataRepository {
    // Invoked from other thread
    fun loadData() : Data // Notice that loadData now returns a non-nullable type
} 

如果我们然后尝试与前面示例中相同的等待表达式:

val data : Data = await untilCallTo { dataRepository.loadData() } has {
    value == "Something"
}

我们会得到一个编译时错误:

Error:(160, 20) Kotlin: Type mismatch: inferred type is AwaitilityKtUntilFunCondition<Data> but AwaitilityKtUntilFunCondition<Data?> was expected
Error:(160, 68) Kotlin: Type inference failed. Please try to specify type arguments explicitly.

这是(当然)正确的!

问题

我想要做的是以某种方式修改has 方法以强制返回类型始终是作为参数传入的类型的不可为空等价物(可以为可空或不可为空)。我试图做这样的事情(这是行不通的):

infix fun <T, T2> AwaitilityKtUntilFunCondition<T>.has(pred: T2.() -> Boolean): T2
        where T : Any?, // Any? is not required but added for clarity
              T2 : T!! // This doesn't compile
        = factory.until(fn) { t: T ->
                if (t == null) {
                    false
                } else {
                    pred(t as T2)
                }
} as T2

由于T2 : T!!,这无法编译,但我希望它表明了我的意图。 IE。我想以某种方式将T2 定义为:

  1. 如果T 可以为空,则类型为T 的不可为空等效项
  2. 如果T 是不可为空的类型,则与T 相同

这在 Kotlin 中可行吗?

更新:

我在 Awaitility 项目中创建了一个名为 has-with-non-nullable-type 的分支,您会在其中看到我在文件 KotlinTest 中讨论的编译时错误。这就是我想要编译的。您可以使用以下方法克隆它:

$ git clone https://github.com/awaitility/awaitility.git

更新 2:

我添加了gist,我认为它在不使用任何依赖项的情况下演示了问题。

【问题讨论】:

    标签: generics kotlin


    【解决方案1】:

    我创建了一个最小的例子来实现你想要的:

    fun <T: Any> test(t: T?): T {
        // ...
        return t as T
    }
    

    您为T 定义了一个上限Any,因此它不能是null。对于参数t,您使用类型T?。最后你返回t 转换为T

    示例:

    val a: String = test("Hello")
    val b: String = test(null)
    

    【讨论】:

    • 我听到了,但是当我尝试这样做时,它并不能解决我在等待中的实际问题。我不知道这个问题是否应该更清楚,或者它是否放错了地方。我知道有很多问题要问,但我已经添加了关于如何克隆项目的说明,并创建了一个新分支,如果你愿意的话,它会准确地显示问题。
    • @Johan 这到底是什么问题?这应该可以,但需要您将 T?T 放在正确的位置
    • @Zoe 问题是,如果我尝试 Willi 的建议,它不会使 my code 编译我想要的。
    • 顺便说一句,我很高兴对我的问题提出改进建议。我已经尽力做到清楚,但我确信它可以改进。
    • @Johann 创建了一个最小的自包含示例(无依赖项),它准确地显示了我的答案中解释的概念不适用的内容。只有这样,我们才能缩小问题的范围。我认为这个问题真的很有趣:)
    【解决方案2】:

    AwaitilityKtUntilFunCondition可以变成contravariant (so AwaitilityKtUntilFunCondition&lt;T&gt; is a subtype of AwaitilityKtUntilFunCondition&lt;T?&gt;),你的gist的这个修改似乎满足了要求:

    // Fake Awaitility DSL
    data class AwaitilityKtUntilFunCondition<out T>(val factory: ConditionFactory, val fn: () -> T)
    
    infix fun <T : Any> AwaitilityKtUntilFunCondition<T?>.has(pred: T.() -> Boolean): T = factory.until(fn) { t: T? ->
        if (t == null) {
            false
        } else {
            pred(t)
        }
    }!!
    
    class ConditionFactory {
        fun <T : Any?> until(supplier: () -> T, predicate: (T) -> Boolean): T {
            val result = supplier()
            return if (predicate(result)) {
                result
            } else {
                throw IllegalArgumentException("Supplied value is not matching predicate")
            }
        }
    }
    
    class Await {
        infix fun <T> untilCallTo(supplier: () -> T): AwaitilityKtUntilFunCondition<T> {
            val conditionFactory = ConditionFactory()
            return AwaitilityKtUntilFunCondition(conditionFactory, supplier)
        }
    }
    
    // Example
    
    data class Data(var state: String)
    interface DataRepository<T> {
        fun loadData(): T
    }
    
    val nullableDataRepository: DataRepository<Data?> = TODO()
    val nonNullableDataRepository: DataRepository<Data> = TODO()
    
    
    // Both of these compile
    val data1: Data = Await() untilCallTo { nonNullableDataRepository.loadData() } has {
        state == "something"
    }
    
    val data2: Data = Await() untilCallTo { nullableDataRepository.loadData() } has {
        state == "something"
    }
    

    【讨论】:

      猜你喜欢
      • 2017-06-25
      • 1970-01-01
      • 1970-01-01
      • 2017-09-25
      • 2021-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-08
      相关资源
      最近更新 更多