Kotlin的泛型
与Java一样,Kotlin也提供泛型。泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。可以为类型安全提供保证,消除类型强转的烦恼。声明泛型类的格式如下:
class Box<T>(t: T) {
var value = t
}
用泛型创建类的实例的时候需要指定类型参数:
val box: Box<Int> = Box<Int>(100)
// 或者
val box = Box(100) // 声明泛型类时候编译器会进行类型推断,100的类型是Int,所以编译器知道我们说的是 Box<Int>。
定义泛型类型变量,可以完整地写明类型参数。如果定义泛型类的时候指定了泛型类型,则编译器可以自动推断类型参数,定义时就可以省略类型参数。
fun <T> boxIn(value: T) = Box(value)
val box4 = boxIn<Int>(1)
val box5 = boxIn(1) // 编译器会自动进行类型推断
在调用泛型函数时,如果可以推断参数类型,就可以省略泛型参数。
例如如下示例,泛型函数根据传入的不同类型做相应处理:
fun <T> printType(content: T) {
when (content) {
is Int -> println("整型参数为 $content")
is String -> println("字符串参数转换为大写:${content.toUpperCase()}")
else -> println("传入参数 T 既不是整型,也不是字符串")
}
}
fun main(args: Array<String>) {
val num = 111
val name = "Weyen"
val bool = true
printType(num) // 整型
printType(name) // 字符串类型
printType(bool) // 布尔型
}
对应的输出结果为:
泛型约束
跟Java一样,Kotlin也拥有泛型约束。在Java中,使用extends关键字指明上界。在kotlin中使用:对泛型的类型上限进行约束。最常见的约束是上界(upper bound)。
例如,下面的代码中,调用num()函数时,传入的参数只能是Number及其子类,如果是其他类型,则会报错:
fun <T : Number> sum(vararg param: T) = param.sumByDouble { it.toDouble() }
fun main() {
val va1 = sum(1,10,0.6)
val va2 = sum(1,10,"kotlin") // 这里会提示编译错误
}
默认的上界是Any?。(注意,这里是Any?不是Any。Any 类似于 Java 中的 Object,它是所有非空类型的超类型。但是 Any 不能保存 null 值,如果需要 null 作为变量的一部分,则需要使用 Any?。Any?是 Any 的超类型,所以 Kotlin 默认的上界是 Any?)
如果有多个上界约束条件,可以用 where 子句:
open class ClassA
interface InterfaceB
class TypeClass<T>(var variable: Class<T>) where T : ClassA, T : InterfaceB
型变
Kotlin 中没有像java一样的<? extends T>这样的通配符,也没有父类向子类转换,取而代之的是两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。
声明处型变:声明处的类型变异使用协变注解修饰符:in、out (消费者 in, 生产者 out。也可以这样理解:in,就是只能作为传入参数的参数类型;out, 就是只能作为返回类型参数的参数类型。)
相对于Java的概念:
out 协变:类型向上转换,像java中的子类向父类转换。
in 逆变:类型向下转换,父类向子类转换。
协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:
// 支持协变的类
class KotlinChange<out A>(val demo: A) {
fun foo(): A {
return demo
}
}
fun main(args: Array<String>) {
var strCo: KotlinChange<String> = KotlinChange("a")
var anyCo: KotlinChange<Any> = KotlinChange<Any>("b")
anyCo = strCo
println(anyCo.foo()) // 对应的控制台输出 a
}
逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:
// 这个类支持逆变,注意这里的in
class KotlinChange<in A>(num: A) {
fun foo(num: A) {
}
}
fun main(args: Array<String>) {
var strDCo = KotlinChange("a")
var anyDCo = KotlinChange<Any>("b")
strDCo = anyDCo
println(strDCo.foo()) //这里就会报错了
}
星号投影(star-projection)
(星号(型)投影(射),单词翻译过来的叫啥都有,能明白意思就行)
在Kotlin 的泛型封装里,会出现 <*> ,这称为星号投影语法,用来表明"不知道关于泛型实参的任何信息"。
<*>星号投影,表示“不知道关于泛型实参的任何信息”,在修饰容器时,因为不知道是哪个类型,所以并不能向容器中写入任何东西(写入的任何值都可能会违反调用代码的期望)。读取值是可以的,因为所有存储在列表中的值都是Any?的子类。<*>星型投影,修饰的容器(比如:MutableList,MutableMap ),只能读不能写。 相当于<out Any?>。
比如:MutableList<*> 表示的是 MutableList<out Any?>
如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投影. 比如, 如果类型定义为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?> 。
如下示例:
class KotlinBean<T>(val t: T, val t2 : T, val t3 : T)
class FruitBean(var name : String)
fun main(args: Array<String>) {
val a1: KotlinBean<*> = KotlinBean(12, "String", FruitBean("苹果")) //星号投影
val a2: KotlinBean<Any?> = KotlinBean(12, "String", FruitBean("苹果")) //和a1是一样的
val apple = a1.t3 //参数类型为Any
println(apple)
val apple2 = apple as FruitBean //强转成FruitBean类
println(apple2.name)
//使用数组
val list:ArrayList<*> = arrayListOf("String",1,3.14f,FruitBean("苹果"))
for (item in list){
println(item)
}
}
对应的输出为:
这时我们可以换个角度理解,关于星号投影,其实就是*代指了所有类型,相当于Any?。
End,如有问题请留言讨论。