Kotlin 第十二章:泛型

来源:互联网 发布:如何解决软件危机 编辑:程序博客网 时间:2024/06/15 11:13

Kotlin 第十二章:泛型

泛型,即“参数化类型”,顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。

泛型

在 Java 中,泛型的使用时比较广泛的,比如:

class Box<T> {    private T var ; }// 使用Box<String> box = new Box<String>();

在 Kotlin 中也有泛型的概念,比如:

class Box<T>(t: T) {    var value = t}// 使用val box: Box<Int> = Box<Int>(1)

但如果类型有可能是推断的,比如来自构造函数的参数或者通过其它的一些方式,一个可以忽略类型的参数:

val box = Box(1)//1是 Int 型,因此编译器会推导出我们调用的是 Box<Int>

而在Java中是不能这样做。

变化

在这里我们先来了解下 Java 泛型中的通配符,在 Effective Java 中 Item 28 中写到: Use bounded wildcards to increase API flexibility,意思是使用通配符为了提高 API 的使用灵活性。

Java 中使用 ? 来表示通配符:

public static <T extends Comparable<? super T>> void sort(List<T> list) {    Object[] array = list.toArray();    Arrays.sort(array);    int i = 0;    ListIterator<T> it = list.listIterator();    while (it.hasNext()) {        it.next();        it.set((T) array[i++]);    }}

在 Java 中,泛型是不可变的,如 List<String> 不是 List<Object> 的子类:

List<String> strs = new ArrayList<String>();List<Object> objs = strs; // 这是错误的,类型不匹配

如果上面的操作是正确的,那么会在使用时造成类型不匹配的问题:

objs.add(1); // 添加一个int类型到StringList里面String s = strs.get(0); // 会报ClassCastException: Cannot cast Integer to String的错误

所以 Java 的泛型会被设计成不可变的类型,就是为了确保运行时类型安全,但是这样同样会带来一些影响。
举个例子,定义一个泛型接口 Collection,里面有 addAll() 方法:

interface Collection<E> ... {    void addAll(Collection<E> items);}

由于 Java 的泛型是不可变的,所以下面的代码是做不到的:

void copyAll(Collection<Object> to, Collection<String> from) {     to.addAll(from); // 这是错误的,Collection<String>不是Collection<Object>的子类}

为了解决上面的问题,Java 中使用了类型通配符方式,如 ? extends T 表示 TT 的子类参数都可以使用,所以 CollectionaddAll() 方法是这样写的:

interface Collection<E> ... {    void addAll(Collection<? extends E> items);}

同样的原理,在上面的代码可以改成这样:

List<String> strs = new ArrayList<String>();strs.add("0");objs.add("1"); List<? extends Object> objs = strs; // OK

通配符上界

<? extends T> (T 表示通配符的上界),表示可以接收 T 以及 T 的子类参数,也就是说可以安全的读取到 T 的实例,事实上所有的集合元素都是 T 的子类的实例,但不能向其添加元素,因为没法确定添加的实例类型跟定义的类型是否匹配,举个栗子:

List<String> strs = new ArrayList<String>();strs.add("0");strs.add("1");List<? extends Object> objs = strs;// 上面说过这样是可以objs.get(0); // 可以获取
// 但是再添加一个int类型的话objs.add(1); // 报错

objs.add-1-

// 再添加一个String类型objs.add("1"); // 同样会报错

objs.add-“1”-

上面的例子说明了 objs 可以读取值,但是再往 objs 里面添加值的时候,就会出错,没法确定添加的实例类型跟定义的类型是否匹配。

这种 wildcard 是通过继承一个范围类 (extends-bound),也就是通配符上界 (upper bound) 来实现类型协变。

通配符下界

那么有通配符上界 <? extends T>,自然就会有下界,<? super T>,其中 T 就表示通配符的下界。

举个栗子:Collection<? super String>Collection<String> 的父类型,所以可以直接 addset,但是 get 的时候获取到的类型是 Object 而不是 String 类型。

List<String> strs = new ArrayList<String>();strs.add("0");strs.add("1");List<? super String> objs = strs;objs.add("1");objs.set(0, "2");Object s = objs.get(0);

在 Kotlin 中,并没有上面的机制,而是通过 Declaration-site varianceType projections 来执行的。

泛型函数

Kotlin 同样支持泛型函数:

fun <T> singletonList(item: T): List<T> {    // ...}
fun <T> T.basicToString() : String {  // extension function    // ...}

使用的时候,在函数名称后面指定具体的类型参数:

val l = singletonList<Int>(1)

声明位置变化

声明位置变异:通过将参数 T 注解成只能作为返回值,不能作为传入参数;使用 out 关键字标识。
首先我们来看一下,在 Java 中,

interface Source<T> {    public T nextT();}public void demo(Source<String> strs){    Source<Object> objs = strs; // 在Java中是不允许的    // 正确方式为    // Source<? extends Object> objs = strs;}

在 Kotlin 中,使用声明位置变异来解决这种问题:

abstract class Source<out T> {    // 使用out的话,T只能作为返回值    abstract fun nextT(): T    // 不能作为传入参数,下面会报错    // abstract fun add(value: T)}fun demo(strs: Source<String>) {    val objects: Source<Any> = strs}

out 就有 ininout 互补,它使类型参数逆变 contravariant,只能作为传入参数,不能作为返回值:

abstract class Source<in T> {    // 使用in的话,只能作为传入参数,不能作为返回值    // abstract fun nextT(): T    abstract fun add(value: T)}fun demo(strs: Source<Number>) {    val objects: Source<Double> = strs // Double是Number的子类型}

总结一下,当一个泛型类 C,包含 out 关键字的时候,等同于 Java 的 extends,将类 C 称为 T 的协变类,T 只能作为该类中函数的返回类型,不能作为参数传递进来,也可以称类 CT 的生产者(Producer)。

同理,当包含 in 关键字的时候,等同于 Java 的 super,将类 C 称为 T 的逆变类,T 只能作为该类中函数的参数传递进来,不能作为返回类型,也可以称类 CT 的消费者(Consumer)。

可以将上面两段话总结成:

Consumer in, Producer out!
fun copy(from: Array<out String>, to: Array<in String>) {    assert(from.size == to.size)    for (i in from.indices)         to[i] = from[i]}// 等同于public void copy(List<? extends String> from, List<? super String> to) { ... }

后记

Kotlin 在后面的学习中是越学习越到困难和吃力,还好找到了大神的简书一直做参考给了不少帮助,这一篇学习文章虽然是写完了,但还是感觉懵懵懂懂,希望以后能有进步。希望各位看官能够不吝啬提出宝贵意见。

参考

Kotlin中文文档

叫我旺仔的简书