摆弄以下示例代码让我意识到我对 Kotlin 何时推断平台类型感到困惑:
inline fun <reified U, V> LiveData<U>.map(crossinline f: (U) -> V): LiveData<V> {
val result = MediatorLiveData<V>()
result.addSource(this) { u -> // u: U!
if (u is U) {
result.value = f(u)
}
}
return result
}
val x = MutableLiveData<Int>()
val y = x.map { it + 1 }
x.value = 1 // ok
x.value = null // will eventually crash from doing null + 1
我希望 reified+crossinline 的使用能够防止 map
调用 f
的函数当u
是 null
,因为它会检查是否 null is Int
。但有趣的是,null
偷偷溜过支票。
我想我一定是误解了 reified+crossinline 的工作原理,但似乎真正的问题是类型推断: U
似乎被推断为Int!
,不是Int
。例如,手动指定 map
的类型参数使事情正常工作:
val y = x.map<Int, Int> { it + 1 }
所以。为什么 Kotlin 会推断出上面的平台类型? (毕竟, x
推断出类型 MutableLiveData<Int>
,而不是 MutableLiveData<Int!>
。)除了自己进行推断之外,还有没有办法不推断平台类型?
最佳答案
在深入了解 Kotlin 的复杂性(例如具体化泛型)之前,您应该问的第一个问题是:为什么我可以这样做?
val x = MutableLiveData<Int>()
x.value = null
这个问题的答案源于这样一个事实:MutableLiveData
是用 Java 编写的,而不是 Kotlin。
如果你用 Kotlin 编写这个类:
class MyKotlinClass<T: Any> {
lateinit var value: T
}
然后尝试这样做:
val x = MyKotlinClass<Int>()
x.value = null
那么它就无法编译。这是因为您已将类型定义为不可为 null 的 Any
。
但是想象一下,用 Java 编写这样一个泛型类,其中 Any
是 Object
,并且除了提供注释之外,您实际上无法定义任何内容的可空性:
class ExampleJavaClass<T> {
public ExampleJavaClass(T param) {
}
static ExampleJavaClass getStringInstance() {
return new ExampleJavaClass<String>(null);
}
}
这是允许的。因此,为了允许与 Java 完全兼容的互操作,Kotlin 必须假设 null 是可以的。不幸的是,对你来说,它是不可见的。
除非有人生成了 LiveData 的 Kotlin 特定实现来加强这一点,否则您必须处理 null,或者您可以自己编写一个,例如 this 。我不一定鼓励您自己编写 - 可能会产生不利后果。
在许多情况下,如果您拥有 Java 代码,则可以通过注释 Java 来定义可空性来改善互操作情况,例如@NonNull
。但是,您似乎无法使用类型参数上的现有注释来执行此操作。
关于generics - 对 Kotlin 何时推断平台类型感到困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58580915/