当前位置:嗨网首页>书籍在线阅读

09-类型投影

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

8.2.4 类型投影

大多数情况下,将类型参数T声明为out是非常方便的,而且能避免使用处子类型化带来的麻烦。但在有些情况下,不能限制为只返回T,Array就是一个很好的例子。Array类的代码如下。

class Array<T>(val size: Int) {
    fun get(index: Int): T { /* …… */ }
    fun set(index: Int, value: T) { /* …… */ }
}

在上面的Array类中,T既是get方法的返回类型,也是set方法的第二个参数。也就是说,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)    //编译错误,expects(Array<Any>, Array<Any>)

上面的代码编译是不通过的,因为Array\对于类型参数T是不可变的,Array\和Array\都不是对方的子类型。这其实是由类型不匹配导致的,比如from的参数类型为String,而实际上传入的是Int数组,编译时就会出现ClassCastException异常。

因此,为了避免这种不安全的操作,可以使用类型投影(type projection)来加以限制,使用类型投影限制的代码如下。

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

使用类型投影后,from不再是一个简单的数组,而是一个受限(投影)类型:只能够调用那些返回类型为T的方法,在本例中则只能调用get()。这也是Kotlin使用型变的目的之一,它的作用和Java的Array<? extends Object>相同,但更加简单。当然,也可以使用关键字in来定义一个投影类型。

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

代码中,Array\对应Java的Array<? super String>,也就是说,可以传递一个CharSequence数组或Object数组给fill函数。