Kotlin - 泛型?还是泛型?
kotlin的泛型有in
, out
, where
。
概念
其中有3个概念,分别是协变,逆变,不变。
不变
我们这里有一段代码:
class Box<T>(val value: T)
suspend fun main() {
val stringBox: Box<String> = Box("Hello")
val anyBox: Box<Any> = Box(42)
// val anotherAnyBox: Box<Any> = stringBox // BUILD FAILED
// val anotherCsBox: Box<CharSequence> = stringBox // failed
}
在上述代码中,尝试将 Box<String>
赋值给 Box<Any>
会导致编译错误。因为在 Kotlin 中,泛型类型默认是不变的(invariant)。这意味着即使 String
是 Any
的子类型,Box<String>
也不能被认为是 Box<Any>
的子类型。这个设计是为了确保类型安全性,防止在类型转换时出现运行时错误。
协变
为了解决上面的这个奇怪问题,就得用协变了(out
),协变指的是如果有一个类型T,它可以被替换为它或者它的子类型。
class Box<out T>(val value: T)
suspend fun main() {
val stringBox: Box<String> = Box("Hello")
val anyBox: Box<Any> = Box(42)
val anotherAnyBox: Box<Any> = stringBox
}
这里Box<Any>
,T为Any
,Any
可以替换为他的子类型String
,这就是协变。
逆变
。接下来我们对Box进行一次小小的升级,然后喜提爆炸,
class Box<out T>(val value: T) {
fun equal(other: T): Boolean { // build failed: Type parameter T is declared as 'out' but occurs in 'in' position in type T
return value == other
}
}
这段代码的问题在于 Box<out T>
中的 out
关键字。out
关键字表示这个类型参数 T
只能用作输出(返回值),不能用作输入(参数)。但是,在 equal
方法中,将 T
用作了输入参数,这违反了协变的规则。
解决方法
消除
out
将other类型改为
Any?
那么怎么样才能使用协变呢?假设有一个家庭聚会,
class FamilyInvitation<in T> {
fun invite(member: T) {
println("Inviting: $member")
}
}
fun main() {
val grandfatherInvitation: FamilyInvitation<Grandfather> = FamilyInvitation()
val fatherInvitation: FamilyInvitation<Father> = grandfatherInvitation // 合法,因为 Grandfather 是 Father 的超类
val sonInvitation: FamilyInvitation<Son> = fatherInvitation // 合法,因为 Father 是 Son 的超类
sonInvitation.invite(Son()) // 可以向邀请名单中添加 Son 类型的成员
}
总结
协变(Covariance):使用
out
关键字。如果我们有一个Box<out T>
类型的盒子,那么Box<Son>
可以被赋值给Box<Father>
,因为Son
是Father
的子类。这意味着我们可以从盒子中获取Father
类型的成员。逆变(Contravariance):使用
in
关键字。如果我们有一个FamilyInvitation<in T>
类型的邀请名单,那么FamilyInvitation<Grandfather>
可以被赋值给FamilyInvitation<Father>
,因为Grandfather
是Father
的超类。这意味着我们可以向邀请名单中添加Father
类型的成员。
绝对不可空约束
import org.jetbrains.annotations.*;
public interface Game<T> {
public T save(T x) {}
@NotNull
public T load(@NotNull T x) {}
}
这个是java的声明一个泛型非空,但是在kotlin这一过程大幅简化。
interface ArcadeGame<T1> : Game<T1> {
override fun save(x: T1): T1
// T1 is definitely non-nullable
override fun load(x: T1 & Any): T1 & Any
}
当仅使用 Kotlin 时,不太可能需要明确声明绝对非可空类型,因为 Kotlin 的类型推断会自动处理这个问题。
具体化
当我们需要做一定的推断的时候,例如,我们需要一个Pair里面两个参数必须是xxx类型,可以通过inline
函数使用reified
去实现。
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
if (first !is A || second !is B) return null
return first as A to second as B
}
val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)
val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details
fun main() {
println("stringToSomething = " + stringToSomething)
println("stringToInt = " + stringToInt)
println("stringToList = " + stringToList)
println("stringToStringList = " + stringToStringList)
//println(stringToStringList?.second?.forEach() {it.length}) // This will throw ClassCastException as list items are not String
}