Kotlin Reference (十四) Generics

most from reference

泛型

和Java一样,Kotlin中的类可能有类型参数:

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

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

var 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>

变化

Java类型中最棘手的部分之一就是通配符类型(请参考Java泛型常见问题解答)。Kotlin没有,相反,还多了两点:声明站点变量和类型预测。
首先。我们来想想为什么Java需要这些神秘的通配符。问题在Effective Java中解释,项目28:使用有界通配符来增加API的灵活性。首先,泛型类型在Java中是不变的,也就是说List不是List的子类。为什么这样呢?如果List是可变的,它将不会比Java的数组更好,因为以下代码将在运行时编译并引发异常:

// Java
List<String> strs = new ArrayList<String>();
List<Object> objs = strs; // !!! The cause of the upcoming problem sits here. Java prohibits this!
objs.add(1); // Here we put an Integer into a list of Strings
String s = strs.get(0); // !!! ClassCastException: Cannot cast Integer to String

所以J,Java禁止这样的事情以保证运行时的安全。但也有一些影响。例如addAll从Collection接口考虑方法。这种方法的签名是什么?直观地,我们这样说:

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

但是,我们将无法做如下简单的事情(这是完全安全的):

// Java
void copyAll(Collection<Object> to, Collection<String> from) {
  to.addAll(from); // !!! Would not compile with the naive declaration of addAll:
                   //       Collection<String> is not a subtype of Collection<Object>
}

(在Java中,我们以艰难的方式学习了本课程,请参考有效Java)。
这就是为什么addAll()的实际签名如下:

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

该通配符类型参数 ? extends E表明,该方法接受对象的集合E 或某些亚型 E,而不仅仅是E本身。这意味着我们可以安全地从项目中读取 E(该集合的元素是E的子类的实例),但不能写入它,因为我们不知道什么对象符合该未知子类型E。作为回报这个限制,我们有所期望的行为:Collection 是一个子类型Collection

声明位置差异

假设我们有一个通用接口Source,没有任何方法T作为参数,只有返回的方法T:

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

然后,存储Source对类型变量的实例的引用是完全安全的Source 没有调用消费者的方法。但Java不知道这一点,但仍然禁止:

// Java
void demo(Source<String> strs) {
  Source<Object> objects = strs; // !!! Not allowed in Java
  // ...
}

为了解决这个问题,我们必须声明类型的对象Source

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

fun demo(strs: Source<String>) {
    val objects: Source<Any> = strs // This is OK, since T is an out-parameter
    // ...
}

一般的规律是:当一个类型参数T的一类C被声明出来,可能只发生在它出来的成员-位C,但作为回报,C可以安全地的超类型C。
在“聪明的话”中,他们表示该类在参数中C是协变的T,或者T是一个协变类型的参数。你可以认为C作为一个制片人的T年代,而不是一个消费者的T的。
该出修饰符被称为方差注释,并且由于它是在类型参数声明的网站提供的,我们谈论的声明站点变化。这与Java的使用站点方差形成对照,其中类型用途中的通配符使类型协变。
除了外,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 has type Double, which is a subtype of Number
    // Thus, we can assign x to a variable of type Comparable<Double>
    val y: Comparable<Double> = x // OK!
}

类型投影

声明类型参数T能够更方便避免类型转换的麻烦,但是有些类不一定要返回T,如下:

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

这个函数是把一个数组复制到另一个数组中,我们这样写:

val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" } 
copy(ints, any) // Error: expects (Array<Any>, Array<Any>)

在这里,我们碰到了相同的问题,Array是不变的,不属于Array和Array任何一个的子类。所以,我们强制类型转化的话,会抛出类型转换的异常。

fun copy(from: Array<out Any>, to: Array<Any>) {
 // ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值