前言
在Kotlin中,总的来说,可以理解泛型为:
①向上解决类型不通用
②向下解决类型限定
如果看着累,建议先看小结,寥寥几字,大致了解下。
需要理解几个问题?
① 理解 型变是什么?逆变又是什么?
② 泛型存在的价值是什么?
③ 泛型注解out、in有什么用?Invariant又是什么?
④ 类型擦除 有什么影响?
本节的目录结构是这样的
- 泛型说明
- 型变
- 类型投影
- 泛型函数
- 泛型约束
- 类型擦除
泛型说明
Kotlin的泛型,功能与 Java 一样
看一个范例
fun main(args: Array<String>) {
val age = 23
val name = "runoob"
val bool = true
doPrintln(age) // 整型
doPrintln(name) // 字符串
doPrintln(bool) // 布尔型
}
fun <T> doPrintln(content: T) {
when (content) {
is Int -> println("整型数字为 $content")
is String -> println("字符串转换为大写:${content.toUpperCase()}")
else -> println("T 不是整型,也不是字符串")
}
}
输出了什么呢
整型数字为 23
字符串转换为大写:RUNOOB
T 不是整型,也不是字符串
这便是泛型了,范例中允许接受不同的类型,类型通用。
型变
即类型转变。通过转变类型,提升API的适配度。
java为什么需要通配符来提升API的灵活性?
我们来看看Java的型变是怎么样的。
- Java的泛型是不型变的
- 要想实现多类型的列表存储,就需要有很多个不同的定义,这就很麻烦了
- 必须要有一个 xxx 提供一个通用的解决方案,
否则,列表跟数组又有何区别~
声明处型变(declaration-site variance)
即在声明变量、参数时就确定T的类型。
说起来有点绕口,来看范例,进行一下对比
// Java
void demo(Source<String> strs) {
Source<Object> objects = strs; // !!!在 Java 中不允许
// ……
}
// Kotlin
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
// ……
}
注意看这里Source部分
- java是不支持直接转变的
- Kotlin支持
在Source是使用处,Kotlin的型变解决了 子类型化的麻烦。
这里引入一个概念,型变注解:这里的out
同样的型变注解还有 in
类型投影(type projections)
投影就是 某事物的影子,影子之下才有天地。
只有在影子内的东西方可以动。
说的就是 限定了。
使用处型变:类型投影
这讲的是一种将 某个类型投影 到某处。
你想想一下,只允许投影之下的暗处,这不正是限制吗?
类型投影,不就是类型限制吗?怎么限定呢?通过out\in的类型注解。
来看个范例
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
fun main() {
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // 这里编译是不通过的,ints类型为 Array<Int> 但此处期望 Array<Any>
}
会碰上什么问题呢?
Array 在 T 上是不型变的,因此 Array 和 Array 都不是另一个的子类型。
为什么? 再次重复,因为 copy 可能做坏事。为了避免这种错误,编译器禁止了这种操作。
我们唯一要确保的是 copy() 不会做任何坏事。我们想阻止它写到 from去。
fun copy(from: Array, to: Array) { …… }
这便是类型投影。所有out(协变)是输出,in(逆变)是输入。
复习一下out、in
对于 out 泛型,可以将使用子类泛型的对象 -> 赋值给使用父类泛型的对象。
对于 in 泛型,可以将使用父类泛型的对象 -> 赋值给使用子类泛型的对象。
星投影
这使用于并不知道类型参数的任何信息。 * 代表通吃。
这个泛型类型的所有的实体实例, 都是这个投射的子类型。
看个例子
Function<*, String>
这里用到了*
var list:ArrayList<> = arrayListOf(1) //<>必不可少 相当于java的无泛型
当 cList:ArrayList<> = javaList 时<>相当于
当 javaList:ArrayList<> = cList 时<>相当于
这就是什么都接收了
泛型函数
在简单的学习下,泛型的函数是怎么编写的
fun <T> singletonList(item: T): List<T> {
// ……
}
fun <T> T.basicToString(): String { // 扩展函数
// ……
}
这么用
val l = singletonList(1)
可以省略能够从上下文中推断出来的类型参数
val l = singletonList(1)
泛型约束
这跟java是类似的
//泛型约束 <占位符:类型>
fun <T:Number> play(vararg param: T):Double{
return param.sumByDouble { it.toDouble() }
}
//多个约束,T有多个上限 , where T:类型,T:类型
fun <T> getBetterBig(list:Array<T>,threhold:T):List<T> where T:Number,T:Comparable<T>{
return list.filter { it>= threhold }.sorted()
}
类型擦除
泛型声明的类型安全检测是仅在编译期进行的。
例如,Foo 与 Foo<Baz?> 的实例都会被擦除为 Foo<*>。
运行时泛型类型的实例不保留关于其类型实参的任何信息。
运行时不保留其类型实参的任何信息,即类型擦除。
编译器会禁止由于类型擦除而无法执行的 is 检测
看个熟悉的范例
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list` 会智能转换为 `ArrayList<String>`
}
}
泛型函数调用的类型参数也同样只在编译期检
这便是
在编译器会进行安全类型检查,在运行期则擦除类型,相当于是*类型
小结
泛型,牵扯到 Out (协变)、In(逆变)、Invariant(不变),这是必须要理解的部分。
所谓泛型,即 类型限制,类型转变的称呼。