泛型
Kotlin官网:Classes and Objects-Generics
kotlin中泛型和Java类似
//定义泛型
class Box<T>(t: T) {
var value = t
}
//创建带泛型类的实例
val box: Box<Int> = Box<Int>(1)
//泛型可被推导出
val box = Box(1) // 1 has type Int, so the compiler figures out that we are talking about Box<Int>
创建带泛型的类的实例时,需要声明泛型的类型。
如果泛型类型可以被编译器推测出,则可以省略。
可变泛型
Kotlin中没有Java的通配符泛型,由声明可变和类型投射取代。
在Java中,同一类的不同泛型类型被认为是不同的类,没有继承关系。例如List<String>并不是List<Object>子类。
如果Java允许这样的继承关系,就会出现如下问题:
List<String> strs = new ArrayList<String>();
List<Object> objs = strs;//如过List<String>是List<Object>的子类,可以这样写
objs.add(1);//放入Integer
String s = strs.get(0);//类型转换异常
没有继承关系可以避免这个问题,但是碰到需要宽泛类型的时候又会有问题,比如:
interface Collection<E> ... {
void addAll(Collection<E> items);
}
void copyAll(Collection<Object> to, Collection<String> from) {
to.addAll(from); //虽然这样做是安全的,但是无法通过编译
}
这时需要使用范围泛型
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
Java中extends和super两种,extends时,从集合中取出的对象是E类型,可以使用E的方法和成员,但是无法向集合中添加对象,因为不知道具体是哪种类型;super时,可以向集合中添加E,但是get出来的是Object类型。
当只取出,不做写入操作时,称为Producer;当只写入不取出时,称为Consumer。
在声明泛型处注明可变
Kotlin中在定义泛型的地方声明可变。
Kotlin中对于这两种情况,使用in
和out
声明为范围泛型。
作为Producer,即只取出不写入,使用out
声明,即Java中的extends;作为Consumer时,即只写入不取出,使用in
声明,相当于Java中的super。
这里的out和in意思为“取出”和“写入”
out:
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs //以out声明即Producer,只取出不写入 ,赋值给Any型泛型是安全的
}
in:
interface Comparable<in T> {
operator fun compareTo(other: T): Int
}
fun demo(x: Comparable<Number>) {
x.compareTo(1.0) // Double是Number的子类
val y: Comparable<Double> = x //以in声明即Comsuer,只写入不取出,对于Comparable接口,只处理传入的T类型,无返回,赋值给Comparable<Double>,Double可以作为Number被处理,是安全的
}
类型投射
就是在使用的地方声明范围泛型。使用场景为声明泛型者不能声明为范围泛型。
例如:
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
对于Kotlin数组,能写入能取出,不能声明为in或out的。
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
对于copy方法将一个数组的元素复制到另外一个,此时只能操走Any泛型的数组。
如果认为有继承关系,即可以传入Any子类型泛型,会遇到下面情况:
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) // Error: expects (Array<Any>, Array<Any>)
如果允许这样写,复制的时候会出错,所以Array不认为时Array的子类,此时需要用范围泛型:
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
使用out声明,这样第一个参数可以是任意Any子类的泛型类型,out Any
相当于Java的? extends Any
,第二个参数必须为Array。
同理in可以这样使用:
fun fill(dest: Array<in String>, value: String) {
// ...
}
in String
即相当于Java的? super String
。
星号
当不知道具体泛型类型时使用。
假设T类型,上限是TUpper
- 当使用
Foo<out T>
时,Foo<*>
相当于Foo<out TUpper>
,可以作为TUpper安全取值 - 当使用
Foo<in T>
时,Foo<*>
相当于Foo<in Nothing>
,表示不能写入 - 当使用
Foo<T>
,即不变的泛型,Foo<*>
相当于取值时时Foo<out TUpper>
,写值时是Foo<in Nothing>
当一个类有多个泛型时,例如interface Function<in T, out U>
-
Function<*, String>
相当于Function<in Nothing, String>
-
Function<Int, *>
相当于Function<Int, out Any?>
-
Function<*, *>
相当于Function<in Nothing, out Any?>
in和out总结
in表示处理,只能写入,可以赋值给范围更小的泛型类型,所谓函数参数可以传入范围更大的类型(被更大范围赋值);
out表示取出,只能读取,可以赋值给范围更大的泛型类型,作为函数参数可以传入范围更小的类型(被更小范围赋值)。
泛型函数
函数也可以有泛型,写在方法名前
fun <T> singletonList(item: T): List<T> {
// ...
}
fun <T> T.basicToString() : String { // extension function
// ...
}
使用:
val l = singletonList<Int>(1)
当泛型的类型可以被推测出时可以省略
val l = singletonList(1)
泛型限制
所有可以替代泛型的类型的集称为泛型限制
上限
和Java类似,使用extends定义类型的上限
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
使用:
sort(listOf(1, 2, 3)) //Int是Comparable子类
sort(listOf(HashMap<Int, String>())) //报错,HashMap不是Comparable子类
在不定义时默认的泛型上限是Any?,尖括号中只能定义一个,如果有多个使用where定义在后面
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
类型擦除
和Java类似,泛型只是编译时检查,编译后会被擦出,运行时没有泛型信息。
举个例子,Foo<Bar>
和Foo<Baz?>
都会被擦除成Foo<*>
。
运行时无法使用泛型来检查类型,编译器不允许这样的is检查。
强转成带泛型的类型,可能在逻辑上保证了安全性,编译器无法推测时依然会报警。
运行时的转会只会检查类的类型,不会检查类的泛型的类型。
泛型函数耶类似,泛型信息会被擦出。但是行内函数的具体类型参数的类型信息会被保留,可以用作类型检查和转换,见4-3行内函数。