Kotlin泛型

作者: agile4j | 来源:发表于2018-09-01 20:18 被阅读90次

    作者:刘仁鹏
    参考资料:
    1.https://www.yiibai.com/kotlin/generics.html
    2.https://www.jianshu.com/p/832b9548b331


    • 注:为更好地理解Kotlin中泛型的用法,文中会与Java的泛型机制做对比。默认读者对Java的泛型有基本了解。

    1.型变的概念

    • 型变分两种:协变逆变

    • F类型是C类型的父类型,我们简记为F<-C。则有:

      协变 :当F<-C时,有f(F)<-f(C),则称f是协变的
      逆变 : 当F<-C时,有f(C)<-f(F),则称f是逆变的

    • Java中的泛型是 不型变的,即List<String>并 不是 List<Object>的子类型。

    • 为解决这个问题,Java引入了 通配符 的概念,来提供对 型变 的支持:

    //List<Object> list = new ArrayList<String>(); //编译期错误:Incompatible types.
    List<? extends Object> list = new ArrayList<String>(); //OK
    List<? super String> list2 = new ArrayList<Object>(); //OK
    
    • Java中的 < ? extends T>实现了泛型的协变

    • Java中的 < ? super T>实现了泛型的逆变

    • Kotlin中没有 通配符 的概念,它通过另外两个概念实现了对 型变 的支持:声明处型变类型投影 (后面详细介绍)

    2.PECS

    • Java中的extends和super关键字,被用在什么场景下呢?《Effective Java》中已经给出了答案:

      PECS :producer-extends, customer-super

    • PECS的原因:extends 规定了类型的上限,它被用来作为 生产者 的角色。因为它 能保证该通配符代表的具体类型,可被安全的转型为类型上限,因此作为方法的 返回类型 是安全的。而如果被当做 消费者 ,则不安全:因为它 不能保证该通配符代表的具体类型,到底是类型上限的哪个子类型,所以它不能用来作为方法的 参数类型。与此同理,super 只能被用来作为 消费者 。一个典型的CASE:

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        // 校验略
        ListIterator<? super T> di = dest.listIterator();
        ListIterator<? extends T> si = src.listIterator();
        while (si.hasNext()) {
            di.next();
            T element = si.next(); // src extent 生产者 作为方法返回值类型是安全的
            di.set(element); // dest super 消费者 作为方法参数类型是安全的
        }
    }
    
    • 注意:虽然被extends通配符修饰的对象,是 生成者 的角色,但这并不意味着该对象是 不可变的 :例如你仍然可以调用对象的clear()方法来删除对象内持有的所有元素,因为clear()根本无需任何参数。通配符 保证的唯一事情是 类型安全不可变性 完全是另一回事。

    3.声明处型变

    • 上文提到Kotlin没有通配符的概念,而是通过另两个概念来实现型变。本小节介绍这两个概念中的 声明处型变,下一节讲解另一个概念:类型投影
    • 声明处型变 是指可在泛型 被声明的地方 指定该泛型的 型变属性(协变/逆变) (Java只可在泛型 被使用的地方 制定型变属性)
    • Kotlin的声明处型变通过 out修饰符 表示该泛型是 协变 的,只能被用来当做 生产者,只能出现在 输出位置。而 in修饰符 表示该泛型是 逆变 的,只能被用来当做 消费者,只能出现在 输入位置。例如:
    abstract class Supplier<out T> {
        abstract fun get(): T
        //abstract fun set(t: T) 
        //编译期错误:Type parameter T is declared as 'out' but occurs in 'in' position in type T
    }
    
    abstract class Customer<in T> {
        abstract fun set(t: T)
        //abstract fun get(): T 
        //编译期错误:Type parameter T is declared as 'in' but occurs in 'out' position in type T
    }
    
    • 声明处型变 带来的 好处 是:当一个类 C 的类型参数 T 被声明为 out 时,则C<Base>可安全地作为C<Derived>的 超类。也可说:

      1. C 在泛型 T 上是 协变的
      2. T 是一个协变的 类型参数
      3. CT生产者,而不是 T消费者
    • out 和 in 修饰符称为 型变注解,并且由于它在类型参数 声明处 提供,所以才叫作 声明处型变

    4.类型投影

    • 类型投影 是Kotlin中的 使用处型变
    • 类型投影 存在的必要性:声明式投影 非常方便,但 无法解决一个类既是泛型T的生产者,又是泛型T的消费者的情况,例如:
    abstract class Container<T>(val size: Int) {
        abstract fun get(index: Int): T
        abstract fun set(index: Int, value: T)
    }
    
    • 上述Array类在泛型T上是 不型变 的,即Array<Any>和Array<Int>都不是另一个的子类型。
    • 因此Kotlin仍然需要 使用处型变 的用法,即 类型投影 :被类型投影修饰的 对象,是一个 受限制的(投影的)对象out 修饰的情况下,只可以调用其 返回类型 为类型参数T的方法;in 修饰的情况下,只可以调用其 参数类型 为类型参数T的方法。例如:
    fun copy(from: Array<out Any>, to: Array<Any>) {
        // 校验略
        for (i in from.indices) {
            to[i] = from[i]
            //from[i] = null 
            //编译期错误:Out-projected type 'Array<out Any>' prohibits the use of 'public final operator fun set(index: Int, value: T): Unit defined in kotlin.Array
        }
    }
    
    • 如上,Kotlin中虽然没有 通配符 的概念,但是 类型投影 起到了与之相同的作用:Array<out String> 对当于Java的Array<? extends String>,Array<in String> 对当于Java的Array<? super String>

    5.星投影

    • Kotlin中的星投影跟Java中的原始类型类似,但星投影是安全的。
    • 如果类型被声明为interface Function<in T, out U>,则有以下星投影:
    1. Function< *, String >表示Function< in Nothing, String >
    2. Function< Int, * >表示Function< Int, out Any? >
    3. Function< *, * >表示Function< in Nothing, out Any? >

    end

    相关文章

      网友评论

        本文标题:Kotlin泛型

        本文链接:https://www.haomeiwen.com/subject/itcmmftx.html