Java的泛型不支持型变,Java采用通配符来解决这个问题;Kotlin采用安全的型变代替了Java的通配符。
一、泛型型变的需要
Java泛型的通配符语法默认是有上限的,只不过其上限是Object类。
泛型存在如下规律:
- 通配符上限(泛型协变)意味着从中取出(out)对象是安全的,但传入对象(in)则不可靠。
- 通配符下限(泛型逆变)意味着向其中传入(in)对象是安全的,但取出对象(out)则不可靠。
Kotlin利用上面两个规律,抛弃了泛型通配符语法,而是利用in、out来让泛型支持型变。
二、声明处型变
Kotlin处理泛型型变的规则如下:
- 如果泛型只需要出现在方法的返回值声明中(不出现在形参声明中),那么该方法就只是取出泛型对象,因此该方法就支持泛型协变(相当于通配符上限);如果一个类的所有方法都支持泛型协变,那么该类的泛型参数可使用out修饰。
- 如果泛型只需要出现在方法的形参声明中(不出现在返回值声明中),那么该方法就只是传入泛型对象,因此该方法就支持泛型逆变(相当于通配符下限);如果一个类的所有方法都支持泛型逆变,那么该类的泛型参数可使用in修饰。
下例中定义一个支持泛型协变的类。
package test0709
class User<out T> {
//此处不能用var,否则就有setter方法
//setter方法会导致T出现在方法形参中
val info: T
constructor(info: T) {
this.info = info
}
fun test(): T {
println("执行test方法")
return info
}
}
fun main(args: Array<String>) {
//此时T的类型是String
var user = User<String>("Kotlin")
println(user.info)
//对于u2而言,它的类型是User<Any>,此时T的类型是Any
//由于程序声明了T支持协变,因此User<String>可当成User<Any>使用
var u2: User<Any> = user
println(u2.info)
}
输出结果:
Kotlin
Kotlin
下例中定义一个支持泛型逆变的类。
package test0709
class Item<in T> {
fun foo(t: T) {
println(t)
}
}
fun main(args: Array<String>) {
//此时T的类型是Any
var item = Item<Any>()
item.foo(20)
var im2:Item<String> = item
//im2的实际类型是Item<Any>,因此它的参数只要是Any即可
//声明了im2的类型为Item<String>
//因此传入的参数只可能是String
im2.foo("Kotlin")
}
输出结果:
20
Kotlin
- 如果泛型T只出现在该类的方法的返回值声明中,那么该类泛型形参即可使用out修饰T。
- 如果泛型T只出现在该类的方法的形参声明中,那么该泛型形参即可使用in修饰T。
上面的类都是在声明时使用out或in指定泛型支持型变的,这种方式被称为“声明处型变”。
三、使用处型变:类型投影
如果不能使用声明处型变,则可以使用Kotlin提供的“使用处型变”。所谓使用处型变,就是在使用泛型时对其使用out或in修饰。
下例为使用处协变:
package test0709
fun copy(from: Array<out Any>, to: Array<Any>) {
val len = if (from.size < to.size) from.size else to.size
for (i in 0 until len) {
to[i] = from[i]
}
}
fun main(args: Array<String>) {
var arr1 = arrayOf(2, 3, 5, 7)
var arr2: Array<Any> = arrayOf(4, 12, 15, 19, 100)
copy(arr1, arr2)
println(arr2.contentToString())
}
输出结果:
[2, 3, 5, 7, 100]
下例为使用处逆变:
package test0709
fun fill(dest: Array<in String>, value: String) {
if (dest.size > 0) {
dest[0] = value
}
}
fun main(args: Array<String>) {
var arr1: Array<CharSequence> = arrayOf("a", "b", StringBuilder("cc"))
fill(arr1, "Kotlin")
println(arr1.contentToString())
var intArr: Array<in Int> = arrayOf(2, 5, 39)
println(intArr.contentToString())
intArr.set(0, 34)
var numArr: Array<Number> = arrayOf(3, 4.1, 10.4)
intArr = numArr
println(intArr.contentToString())
}
输出结果:
[Kotlin, b, cc]
[2, 5, 39]
[3, 4.1, 10.4]
Array
四、星号投影
星号投影是为了处理Java的原始类型。
fun main(args: Array<String>) {
//<*>必不可少,相当于Java的原始类型
var list: ArrayList<*> = arrayListOf(1, "Kotlin")
println(list)
}
输出结果:
[1, Kotlin]
如果泛型类型具有多个类型参数,那么每个类型参数都可以单独指星号投影。
假如声明了支持两个泛型参数的Foo<in T,out U>类型,关于星号投影的解释如下:
- 对于Foo<*,String>,相当于Foo<in Nothing,String>
- 对于Foo<Int,*>,相当于Foo<Int,out Any?>
- 对于Foo<*,*>,相当于Foo<in Nothing,out Any?>