Kotlin 基础教程之泛型
kotlin 支持泛型, 语法和 java 类似。
例如,泛型类:
class hello<t>(val value: t) val box = box<int>(1) val box1 = box(2)
泛型函数:
fun <t> foo(item: t): list<t> { // do something } val list = foo<int>(1) fun <t> t.tostring2(): string { // 扩展函数 } fun <k, v>put(key: k, value: v) { // 多个泛型参数 }
类型变异
java 的泛型中,最难理解的就是通配符。java 中使用通配符是由于泛型类型是不可变的,比如 list<string>不是list<object>的子类, 因而 list<object> objs = strs 这样的代码有编译错误。
为了解决此问题,java 提供了通配符类型参数(wildcard type argument)。如果你只能从一个集合取得元素, 那么就可以使用一个 string 组成的集合, 并从中读取 object 实例,这个时候用? extends t. 反过来, 如果你只能向集合 放入 元素, 那么就可以使用一个 object 组成的集合, 并向其中放入 string, 这个时候用? super t。
kotlin 不存在这样的通配符,提供了两种方法:声明处类型变异(declaration-sitevariance), 以及类型投射(type projection)。
假设我们有一个泛型接口 source<t> , 其中不存在任何接受 t 作为参数的方法, 仅有返回值为 t 的方法:
// java interface source<t> { t nextt(); } void demo(source<string> strs) { source<object> objects = strs; // !!! 在 java 中禁止这样的操作 // ... }
为了解决这个问题, 我们不得不将对象类型声明为 source<? extends object> , 其实是毫无意义的, 编译器并不理解这一点。
在 kotlin 中, 我们有办法将这种情况告诉编译器. 这种技术称为声明处的类型变异(declaration-sitevariance): 我们可以对 source 的 类型参数 t 添加注解, 来确保 source<t> 的成员函数只会返回t 类型, 而绝不会消费 t 类型. 为了实现这个目的, 我们可以对 t 添加 out 修饰符:
abstract class source<out t> { abstract fun nextt(): t } fun demo(strs: source<string>) { val objects: source<any> = strs // 这是 ok 的, 因为 t 是一个 out 类型参数 // ... }
一般规则是: 当 c 类的类型参数 t 声明为 out 时, 那么在 c 的成员函数中, t 类型只允许出现在输出位置, 这样的限制带来的回报就是, c<base> 可以安全地用作 c<derived> 的父类型。
除了 out 之外, kotlin 还提供了另一种类型变异注解: in. 这个注解导致类型参数反向类型变异(contravariant): 这个类型将只能被消费, 而不能被生产. 反向类型变异的一个很好的例子是 comparable :
abstract class comparable<in t> { abstract fun compareto(other: t): int } fun demo(x: comparable<number>) { x.compareto(1.0) // 1.0 类型为 double, 是 number 的子类型 // 因此, 我们可以将 x 赋值给 comparable<double> 类型的变量 val y: comparable<double> = x // ok! }
类型投射(type projection)
class array<t>(val size: int) { fun get(index: int): t { /* ... */ } fun set(index: int, value: t) { /* ... */ } }
这个类对于类型参数 t 既不能协变, 也不能反向协变. 这就带来很大的不便。
fun copy(from: array<any>, to: array<any>) { assert(from.size == to.size) for (i in from.indices) to[i] = from[i] } val ints: array<int> = arrayof(1, 2, 3) val any = array<any>(3) copy(ints, any) // 错误: 期待的参数类型是 (array<any>, array<any>)
我们需要确保的就是 copy() 函数不会做这类不安全的操作. 我们希望禁止这个函数向 from 数组
写入 数据, 我们可以这样声明:
fun copy(from: array<out any>, to: array<any>) { // ... }
这种声明在 kotlin 中称为 类型投射(type projection): 我们声明的含义是, from 不是一个单纯的数组, 而是
一个被限制(投射)的数组: 我们只能对这个数组调用那些返回值为类型参数 t 的方法。
也可以使用 in 关键字来投射一个类型。
fun fill(dest: array<in string>, value: string) { // ... }
- 星号投射(star-projection)
- 泛型约束(generic constraint)
对于一个给定的类型参数, 所允许使用的类型, 可以通过 泛型约束(generic constraint) 来限制。
最常见的约束是 上界(upper bound), 与 java 中的 extends 关键字相同:
fun <t : comparable<t>> sort(list: list<t>) { // ... }
对于类型参数 t , 只允许使用 comparable<t> 的子类型. 比如:
sort(listof(1, 2, 3)) // 正确: int 是 comparable<int> 的子类型 sort(listof(hashmap<int, string>())) // 错误: hashmap<int, string> 不是 // comparable<hashmap<int, string>> 的子类型
泛型类型
java 里面的泛型不支持类型, 比如 t.class这样的代码获取不到类型。kotlin 泛型函数通过内联函数可以获取泛型的类型,比如:
inline fun <reified t>runtimetype(): unit { println("my type parameter is " + t::class.qualifiedname) } inline fun <reified t>list<any>.collect(): list<t> { return this.filter { it is t }.map { it as t } }
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!