泛型,即 "参数化类型",将类型参数化,可以用在类、接口、方法上。
与Java语言中的非常相似,但是Kotlin语言的创建者试图通过引入特殊的关键字(如out和in)来使它们更加直观和易于理解。
以下是使用泛型的主要优点:
- 类型安全:通用允许仅保留单一类型的对象。泛型不允许存储其他对象。
- 不需要类型转换:不需要对对象进行类型转换。
- 编译时间检查:在编译时检查泛型代码,以便在运行时避免任何问题
泛型类
像 java 一样,Kotlin 中的类可以拥有类型参数:
class Box(t: T){
var value = t
}
通常来说,创建一个这样类的实例,我们需要提供类型参数:
val box: Box = Box(1)
但如果类型有可能是推断的,比如来自构造函数的参数或者通过其它的一些方式,一个可以忽略类型的参数:
val box = Box(1) //1是 Int 型,因此编译器会推导出我们调用的是 Box
泛型接口
声明泛型接口的格式与声明泛型类相似,定义如下泛型接口。
interface GenericsInterface {
public T generate();
}
在实现泛型接口的类中,指定泛型实参。
class GenericsClass implements GenericsInterface {
public String generate() {
return "hello";
}
}
泛型函数
范型函数
函数也可以像类一样有类型参数。类型参数在函数名之前:
fun singletonList(item: T): List {
// ...
}
fun T.basicToString() : String { // extension function
// ...
}
调用范型函数需要在函数名后面制定类型参数:
val l = singletonList(1)
范型约束
指定类型参数代替的类型集合可以用通过范型约束进行限制。
上界(upper bound):最常用的类型约束是上界,在 Java 中对应 extends关键字:
fun > sort(list: List) {
// ...
}
冒号后面指定的类型就是上界:只有 Comparable
sort(listOf(1, 2, 3)) // OK. Int is a subtype of Comparable
sort(listOf(HashMap())) // Error: HashMap is not a subtype of Comparable>
默认的上界是 Any?。在尖括号内只能指定一个上界。如果要指定多种上界,需要用 where 语句指定:
fun cloneWhenGreater(list: List, threshold: T): List
where T : Comparable,
T : Cloneable {
return list.filter { it > threshold }.map { it.clone() }
}
声明处变型*
java声明处变型
假如有个范型接口Source
// Java
interface Source {
T nextT();
}
存储一个Source
// Java
void demo(Source strs) {
Source
为此,我们不得不声明对象类型为 Source extends Object>,这样做并没有太大的意义,因为我们可以像以前一样调用所有方法,因此并没有通过复杂的类型添加什么值。但编译器不知道。
Kotlin声明处变型
在 Kotlin 中,有种可以将这些东西解释给编译器的办法,叫做声明处变型:通过注解类型参数 T 的来源,来确保它仅从 Source
abstract class Source {
abstract fun nextT(): T
}
fun demo(strs: Source) {
val objects: Source = strs // This is OK, since T is an out-parameter
// ...
}
一般原则是:当一个类 C 的类型参数 T 被声明为 out 时,它就只能出现在 C 的成员的输出-位置,结果是 C
更聪明的说法就是,当类 C 在类型参数 T 之下是协变的,或者 T 是一个协变类型。可以把 C 想象成 T 的生产者,而不是 T 的消费者。
out 修饰符本来被称之为变型注解,但由于同处与类型参数声明处,我们称之为声明处变型。这与 Java 中的使用处变型相反。
另外除了 out,Kotlin 又补充了一个变型注释:in。
它接受一个类型参数逆变:只可以被消费而不可以 被生产。非变型类的一个很好的例子是 Comparable:
abstract class Comparable {
abstract fun compareTo(other: T): Int
}
fun demo(x: Comparable) {
x.compareTo(1.0) // 1.0 has type Double, which is a subtype of Number
// Thus, we can assign x to a variable of type Comparable
val y: Comparable = x // OK!
}
使用处变型:类型投影*
声明类型参数 T 为 out 很方便,而且可以避免在使用出子类型的麻烦,但有些类 不能 限制它只返回 T ,Array 就是一个例子:
class Array(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
这个类既不能是协变的也不能是逆变的,这会在一定程度上降低灵活性。考虑下面的函数:
fun copy(from: Array, to: Array) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
该函数作用是复制 array ,让我们来实际应用一下:
val ints: Array = arrayOf(1, 2, 3)
val any = Array(3) { "" }
copy(ints, any) // Error: expects (Array, Array)
这里我们又遇到了同样的问题 Array
我们想做的就是确保 copy() 不会做类似的不合适的操作,为阻止向from写入,我们可以这样:
fun copy(from: Array, to: Array) {
// ...
}
这就是类型投影:这里的from不是一个简单的 array, 而是一个投影,我们只能调用那些返回类型参数 T 的方法,在这里意味着我们只能调用get()。这是我们处理调用处变型的方法,类似 Java 中Array extends Object>,但更简单。
当然也可以用in做投影:
fun fill(dest: Array, value: String) {
// ...
}
Array
参考文章:
kotlin-in-chinese