Kotlin将可为null的泛型类型强制转换为同一泛型类型的不可为null



首先,我只想指出,我知道将null强制转换为不可为null的类型以及Kotlin泛型和可为null类类型,但我认为这些问题与我的问题不同(如果我错了,请纠正我)。

背景

我正在开发一个名为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"
}

这是因为如果dataRepository.loadData()返回null,则has返回false,并且如果datanull,则从不调用所提供的接收器函数({ value == "Something" })。如果条件不满足,Awaitility也会抛出异常,因此我们知道从表达式返回的内容的类型为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)

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

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

如果我们尝试与前面的例子相同的Awaitility表达式:

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方法,以强制返回类型始终是作为参数传入的类型的不可为null的等价类型(可以为null也可以为不可为Null)。我试过做这样的事情(不起作用):

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!!的原因,这篇文章没有编译,但我希望它能表明我的意图。也就是说,我想以某种方式将T2定义为:

  1. 如果T是可为null的,则类型T的不可为null等价物
  2. 如果T是不可为null的类型,则与T相同

这在科特林可能吗?

更新:

我在Awaitility项目中创建了一个名为has-with-non-nullable-type的分支,您可以在文件KotlinTest中看到我所说的编译时错误。这就是我想要编译的内容。您可以使用进行克隆

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

更新2:

我添加了一些要点,我认为这些要点可以在不使用任何依赖项的情况下演示问题。

我创建了一个实现您所需的最小示例:

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)

AwaitilityKtUntilFunCondition可以反变(因此AwaitilityKtUntilFunCondition<T>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"
}

最新更新