关于什么是泛型, 看另一篇文章 https://www.jianshu.com/p/7eac2f36036e
参数化函数(泛型函数)
- 在函数名前边加上
<T>
实现参数化函数 - 函数的参数至少有一个是T类型, 否则编译器无法推断出T的类型
fun <T> foo (input: T): T {
return input
}
函数的返回值可以是任何类型, 但不能用运行时的类型
fun <T> foo (input: T): String {
return input // 即使我们在运行时传入了一个字符串也不行, input的类型是T, 与运行时的类型无关
}
参数化类型
参数化类型主要用在各种容器类型上, 这种类型内部可以包含其他类型的数据, 比如list, map
class Sequence<T> // 定义一个参数化类型
val seq = Sequence<Int>() //传入Int类型
上界类型约束
将类型限制为某个类的子类, 如果省略, 将会是Any
fun <T: Number> convert(a: T, b: T) // 将T的类型限制为Number的子类
fun <T> convert(a: T, b: T) // T的类型限制为Any
Invariance (不变)
类型不变指泛型类型默认是没有继承上的关系的, M<Int>
并不是M<Number>
的一个子类型.
这样设计的原因, 参考这个例子:
fun foo(m: M<Number>): Unit {
m.add(123L)
}
假设有一个M<Int>型的集合x
, 执行foo(x)
后, 如果类型有继承关系, 由于Int
和Long
都是Number
的子类型, 就会往x
里添加一个Long
型元素, 这违背了类型安全的原则.
Covariance (协变)
协变是改变类型之间的关系, 使他们有继承性.
fun foo(m: M<Number>): Unit {
m.functionFromNumber()
}
假设foo
函数会调用一个Number
类的方法, 这样我们就不用管传入的是m<int>
, 还是m<Long>都无所谓(因为函数定义在父类上), 此时就需要
协变`来让类型有继承关系.
class M<out T>
使用out
将T
定义为协变类型之后, 不能用T
作为函数的输入参数(形参), 可以做返回值. 像m.add(T)
是非法的.
逆变 Contravariance
逆变是反转两个类的继承关系, 比如逆变后, M<Int> 是 M<Number> 的父类.
这个需求是这样的
Event<String>(stringHandler)
Event<Number>(numberHandler)
我们有两种Event
, 分别用对应类型的Handler处理, 假设我们想用一个通用的handler比如: commonHandler<Any>`来处理.
Event<String>(commonHandler)
Event<Number>(commonHandler)
答案是不行, 因为Any
是String
的父类, 而Event
只能使用String
或它的子类. 通过逆变, Any
就变成了String
的子类.
class Event<in T>(val handler: Handler<T>)
class Handler<in T>
...
逆变后, 类型只能作为输入参数(形参), 不能作为返回值类型.
type projection
类型的variance有两种, use site(java) 和 declaration site(Kotlin), type projection 允许我们使用use site variance, 它的思想是, 如果我们无法在类定义(比如使用别人的类)时使用covariance或contravriance(即declaration site), 我们可以在定义函数时, 规定函数将如何使用类型.
fun foo(m: M<out Number>): Unit {
m.functionFromNumber()
}
同理, contravariant
class Event<in T>(val handler: Handler<in T>) // 构造函数的in T
type projection定义了我们如何使用函数, 而不需修改原类型声明
Type reification
由于JVM在编译时会把所有泛型类型(不包括基础数据类型)信息擦除, kotlin同样如此, 使用Type reification, kotlin可以为inline 函数保留泛型类型信息.
inline fun <reified T>printT(any: Any): Unit {
if (any is T)
println("I am a tee: $any")
}
这个函数可以在运行时获取到T
的传入类型. 类型具体化只能用于inline
函数, 因为inline
函数的执行是把函数体内容直接拷贝到调用处, 此时通过传递的参数可以知道T的类型. 其他函数无法使用类型具体化
网友评论