Kotlin快速入门系列8

Kotlin的泛型

与Java一样,Kotlin也提供泛型。泛型,即 "参数化类型",将类型参数化,可以用在类,接口,方法上。可以为类型安全提供保证,消除类型强转的烦恼。声明泛型类的格式如下:

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

用泛型创建类的实例的时候需要指定类型参数:

val box: Box = Box(100)
// 或者
val box = Box(100) // 声明泛型类时候编译器会进行类型推断,100的类型是Int,所以编译器知道我们说的是 Box

定义泛型类型变量,可以完整地写明类型参数。如果定义泛型类的时候指定了泛型类型,则编译器可以自动推断类型参数,定义时就可以省略类型参数。

fun  boxIn(value: T) = Box(value)

val box4 = boxIn(1)
val box5 = boxIn(1)     // 编译器会自动进行类型推断

在调用泛型函数时,如果可以推断参数类型,就可以省略泛型参数。

例如如下示例,泛型函数根据传入的不同类型做相应处理:

fun  printType(content: T) {

    when (content) {
        is Int -> println("整型参数为 $content")
        is String -> println("字符串参数转换为大写:${content.toUpperCase()}")
        else -> println("传入参数 T 既不是整型,也不是字符串")
    }
}

fun main(args: Array) {
    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  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(var variable: Class) where T : ClassA, T : InterfaceB

型变

Kotlin 中没有像java一样的这样的通配符,也没有父类向子类转换,取而代之的是两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。

声明处型变声明处的类型变异使用协变注解修饰符:in、out (消费者 in, 生产者 out。也可以这样理解:in,就是只能作为传入参数的参数类型;out, 就是只能作为返回类型参数的参数类型。)

相对于Java的概念:

out 协变:类型向上转换,像java中的子类向父类转换。

in 逆变:类型向下转换,父类向子类转换。

协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型:

// 支持协变的类
class KotlinChange(val demo: A) {
    fun foo(): A {
        return demo
    }
}

fun main(args: Array) {
    var strCo: KotlinChange = KotlinChange("a")
    var anyCo: KotlinChange = KotlinChange("b")
    anyCo = strCo
    println(anyCo.foo())   // 对应的控制台输出 a
}

逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:

// 这个类支持逆变,注意这里的in
class KotlinChange(num: A) {
    fun foo(num: A) {
    }
}

fun main(args: Array) {
    var strDCo = KotlinChange("a")
    var anyDCo = KotlinChange("b")
    strDCo = anyDCo
    println(strDCo.foo())  //这里就会报错了
}

星号投影(star-projection)

(星号(型)投影(射),单词翻译过来的叫啥都有,能明白意思就行)

在Kotlin 的泛型封装里,会出现 <*> ,这称为星号投影语法,用来表明"不知道关于泛型实参的任何信息"。

<*>星号投影,表示“不知道关于泛型实参的任何信息”,在修饰容器时,因为不知道是哪个类型,所以并不能向容器中写入任何东西(写入的任何值都可能会违反调用代码的期望)。读取值是可以的,因为所有存储在列表中的值都是Any?的子类。<*>星型投影,修饰的容器(比如:MutableList,MutableMap ),只能读不能写。 相当于

比如:MutableList<*> 表示的是 MutableList

如果一个泛型类型中存在多个类型参数, 那么每个类型参数都可以单独的投影. 比如, 如果类型定义为interface Function , 那么可以出现以下几种星号投影:

1、Function<*, String> , 代表 Function ;

2、Function , 代表 Function ;

3、Function<, > , 代表 Function 。

如下示例:

class KotlinBean(val t: T, val t2 : T, val t3 : T)
class FruitBean(var name : String)
fun main(args: Array) {
    val a1: KotlinBean<*> = KotlinBean(12, "String", FruitBean("苹果"))   //星号投影
    val a2: KotlinBean = 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,如有问题请留言讨论。

你可能感兴趣的:(kotlin,开发语言,android)