简书人物Kotlin专题Kotlin

Kotlin学习之泛型

2017-12-05  本文已影响134人  程序员丶星霖

Kotlin学习之泛型

Kotlin的泛型与Java一样,都是一种语法糖,只在源代码里出现,编译时会进行简单的字符替换。

泛型其实就是类型参数,它给强类型编程语言加入了更强的灵活性。

在Java中,只要是有类型的元素,都可以泛型化。泛型类、泛型接口、泛型方法和泛型属性。泛型类和泛型接口统称为泛型类型。最重要的是泛型类型和泛型方法。

在Kotlin,类也可以有类型参数:

class Box<T>(t: T) {
    var value = t
}

一般来说,要创建如上这样类的实例,我们需要提供类型参数:

val box: Box<Int> = Box<Int>(1)

如果类型参数是可以推断出来的,也可以将其省略掉:

val box = Box(1)

在泛型方法的类型参数里可以用冒号指定上界。

fun <T : Comparable<T>> sort(list: List<T>) {/*...*/}

对于多个上界约束条件,可以用where子句:

fun <T> cloneWhenGreater(list: List<T>, threshold: T): List<T>
    where T : Comparable, Cloneable {
      return list.filter(it > threshold).map(it.clone())
    }

一、型变

在Kotlin中,没有通配符类型。但是它有:声明处型变和类型投影。

在Java中的泛型是不型变的。通配符类型参数 ? extends E表示此方法接受E或者E的一些子类型对象的集合,而不只是E自身。也就是说可以安全地从中读取E,但不能写入,因为我们不知道什么对象符合未知的E的子类型。带extends限定的通配符类型使得类型是协变的。

只能从中读取的对象为生产者,只能写入的对象为消费者。通配符保证的唯一的事情就是类型安全。

1.1声明处型变

如果有一个泛型接口Source<T>,这个接口中不存在任何以T为参数的方法,只是方法返回T类性值:

// Java
interface Source<T> {
  T nextT();
}

void demo(Source<String> str) {
  // Java 中这种写法是不允许的
  Source<Object> obj = str;
  /*...*/
}

因为Java中泛型是不型变的,Source<String> 不是Source<Object>的子类型,所以不能把Source<String>类型变量赋值给Source<Object>类型变量。

在Kotlin中,有一种方法向编译器解释这种情况。称为声明处型变:可以标注Source的类型参数T来确保它仅从Source<T>成员中返回,并从不被消费。Kotlin提供了out修饰符:

abstract class Source<out T> {
    abstract fun nextT(): T
}

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // 这个没问题,因为 T 是一个 out-参数
    // ……
}

out修饰符称为型变注解,并且由于它在类型参数声明处提供,所以叫声明处型变

除了out,Kotlin又补充了一个型变注释:in。它使得一个类型参数逆变:只可以被消费而不可以被生产。逆变类的一个很好的例子是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!
}

1.2类型投影

使用处型变:类型投影
将类型参数T声明为out很方便,而且可以避免使用处子类型化的麻烦,但是有些类实际上不能限制为只返回T 。例如Array:

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]
}

要确保的是copy()不会做任何坏事。所以如下:

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ……
}

这就发生了类型投影。这就是使用处型变的用法,并且是对应Java中的Array<? extends Object>,但是使用更简单。

也可以使用in投影一个类型:

fun fill(dest: Array<in String>, value: String) {
    // ……
}
1.2.1星投影

Kotlin为此提供了所谓的星投影语法:

如果泛型类型具有多个类型参数,则每个类型参数都可以单独投影。 例如,如果类型被声明为 interface Function <in T, out U>,我们可以想象以下星投影:

学海无涯苦作舟

我的微信公众号.jpg
上一篇下一篇

猜你喜欢

热点阅读