Kotlin强制将可为空的泛型类型转换为相同的泛型的非空类型吗?

时间:2019-01-18 08:13:14

标签: generics kotlin

首先,我想指出的是,我知道Force a null into non-nullable typeKotlin Generics and nullable Class type,但我认为这些问题与我的不一样(如果我错误)。

背景

我正在开发一个名为Awaitility的库,简而言之,该库旨在等待谓词评估为true为止。 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"
}

之所以有效,是因为如果has返回falsedataRepository.loadData()返回null,并且如果{ value == "Something" }是,则从不调用提供的接收器函数(data)。 null。如果条件不满足,等待性也会引发异常,因此我们知道,从表达式返回的内容类型为Data(而不是Data?),如您在例子。

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?)时非常有效,但如果我们将其传递为非可为空的类型(即{{1} }。例如,如果我们只是将存储库修改为如下所示:

Data

,然后再尝试使用与上一个示例相同的Awaitility表达式:

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. 方法,以强制返回类型始终等于作为参数传递的类型的不可为null的等效对象(可以为可为null或不可为null的类型) )。我试图做这样的事情(不起作用):

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!!定义为:

  1. 如果T2可为空,则类型为T的不可为空
  2. 如果T是不可为空的类型,则与T相同

在科特林有可能吗?

更新:

我在Awaitility项目中创建了一个名为T的分支,您在文件KotlinTest中得到了我正在谈论的编译时错误。这就是我要编译的东西。您可以使用以下方法克隆它:

has-with-non-nullable-type

更新2:

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

2 个答案:

答案 0 :(得分:3)

我创建了一个最小的示例,可以实现您想要的:

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

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

示例:

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

答案 1 :(得分:2)

AwaitilityKtUntilFunCondition可以设为contravariant (so AwaitilityKtUntilFunCondition<T> is a subtype of AwaitilityKtUntilFunCondition<T?>),而对要点的这种修改似乎可以满足要求:

// 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"
}