首先,我想指出的是,我知道Force a null into non-nullable type和Kotlin 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
返回false
,dataRepository.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!!
定义为:
T2
可为空,则类型为T
的不可为空T
是不可为空的类型,则与T
相同在科特林有可能吗?
更新:
我在Awaitility项目中创建了一个名为T
的分支,您在文件KotlinTest中得到了我正在谈论的编译时错误。这就是我要编译的东西。您可以使用以下方法克隆它:
has-with-non-nullable-type
更新2:
我添加了gist,我认为它可以在不使用任何依赖项的情况下演示该问题。
答案 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"
}