结合上一篇java的范型通配符上下届的文章,这里给出kotlin的解决方案。
in, out不仅可以用在定一个使用范型的类时,指定该范型类型只能用在方法的返回值或者输入参数,比如像下边这样:
interface Source<out T> {
fun nextT(): T
}
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs // This is OK, since T is an out-parameter
// ...
}
这就是变向告诉编译器,demo传入的参数Source<String>,该对象对应的所有使用到范型的方法,都只用在方法的返回值上,并且是String类型的。这样在demo函数内部,就可以放心将strs赋值给Souce<Any>变量,因为该变量获取到对象引用后,无论调用对象中什么方法,如果涉及到范型,一定是返回String的,而返回的String值用Any来接,是没有问题的。
但是in, out不仅仅这一种用法, 下边是kotlin官方文档提到的In, out另外一种用法, 就是不是在生命范型类时使用,而是在定义范型函数时使用,其目的也是告诉编译器,去做一些检查防止代码编写人员作出误操作。比如下边的例子,
fun copy(from: Array<out Any>, to: Array<Any>) { ... }
就是告诉编译器,Array<out Any>不是一个普通的数组,而是一个收到限制的数组(编译器施加的限制而非数据结构本身),他只能调用数组中这样的范型函数 - 其返回值是Any类型的。而对于Array中,接受范型作为参数的函数,则不能使用,这样当你往copy函数中传一个Array<String>类型的对象时,就可以放心传送,不用担心在copy函数内部尝试往from对象数组中写入Any类型的元素。
Type projections
Use-site variance: type projections
It is very easy to declare a type parameter T
as out
and avoid trouble with subtyping on the use site, but some classes can't actually be restricted to only return T
's! A good example of this is Array
:
class Array<T>(val size: Int) {
operator fun get(index: Int): T { ... }
operator fun set(index: Int, value: T) { ... }
}
Copied!
This class can be neither co- nor contravariant in T
. And this imposes certain inflexibilities. Consider the following function:
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
Copied!
This function is supposed to copy items from one array to another. Let's try to apply it in practice:
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any)
// ^ type is Array<Int> but Array<Any> was expected
Copied!
Here you run into the same familiar problem: Array<T>
is invariant in T
, and so neither Array<Int>
nor Array<Any>
is a subtype of the other. Why not? Again, this is because copy
could have an unexpected behavior, for example, it may attempt to write a String
to from
, and if you actually pass an array of Int
there, a ClassCastException
will be thrown later.
To prohibit the copy
function from writing to from
, you can do the following:
fun copy(from: Array<out Any>, to: Array<Any>) { ... }
This is type projection, which means that from
is not a simple array, but is rather a restricted (projected) one. You can only call methods that return the type parameter T
, which in this case means that you can only call get()
. This is our approach to use-site variance, and it corresponds to Java's Array<? extends Object>
while being slightly simpler.
You can project a type with in
as well:
fun fill(dest: Array<in String>, value: String) { ... }
Array<in String>
corresponds to Java's Array<? super String>
. This means that you can pass an array of CharSequence
or an array of Object
to the fill()
function.