Kotlin学习笔记3-8 类和对象-泛型

时间:2021-01-28 00:31:43

泛型

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中对于这两种情况,使用inout声明为范围泛型。
作为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行内函数。