Kotlin学习5—泛型

前言

什么是泛型?在我们一般的编程模式下,我们需要给任何一个变量指定一个具体的数据类型,而泛型允许我们不指定具体类型的情况下进行编程,这样会具有更好的扩展性

泛型的基本用法

泛型主要有两种定义方式:定义泛型类,及定义泛型方法,使用的语法结构都是,括号中的字母使用任何字母都可以的,T只是常规写法

在Kotlin中,还拥有非常出色的类型推导机制,假设我们传入一个Int类型的参数,Kotlin能够自动推导出泛型的类型就是Int型

object BaseGeneric {

    @JvmStatic
    fun main(array: Array<String>) {
        val myClass = MyClass<String>()
        println(myClass.method("123"))

        val myClass2 = MyClass2()
        println(myClass2.method(null))
        println(myClass2.method2(789))
        println(myClass2.method3(456))
    }
}

// 定义泛型类
class MyClass<T> {
    fun method(param: T): T {
        return param
    }
}

// 定义泛型方法
class MyClass2 {
    // 泛型的上界默认是 Any?,可传空
    fun <T> method(param: T): T {
        return param
    }
    
    // 泛型的上界为Number类型,指定为数字类型
    fun <T: Number> method2(param: T): T {
        return param
    }

    // 为了指定泛型不可空,可指定泛型的上界默认 Any,不可传空
    fun <T: Any> method3(param: T): T {
        return param
    }
}
泛型的高级特性
对泛型进行实化

Java的泛型功能是通过类型擦除机制来实现的,也就是说泛型对于类型的约束只在编译时期存在,在运行时期仍然会按照JDK 1.5之前的机制来运行,JVM识别不出我们在代码中指定的泛型类型

所有基于JVM的语言,它们的泛型功能都是通过类型擦除机制来实现的,Kotlin也包括在内;

但Kotlin提供了一个新的特性,泛型实化,但怎样实现泛型实化呢?首先该函数必须是内联函数,也就是用inline修饰的函数,其次,在声明泛型的地方必须加上reified关键字来表示该泛型要进行实化

inline fun <reified T> getGenericType() = T::class.java

因此,要实现泛型实化,必须满足两个条件:内联函数和reified关键字修饰

泛型实化的应用

在写Android代码的时候,经常会涉及到Activity的启动,通过启动一个新的Activity的时候,我们需要写该Activity::class.java,因此,我们可以借助泛型的实化来优化这种代码:

    inline fun <reified T> startActivity(context: Context, block: Intent.() -> Unit) {
            val intent = Intent(context, T::class.java)
            intent.block()
            context.startActivity(intent)
        }
泛型的协变

一个泛型类或者泛型接口中的方法,它的参数列表是接收数据的地方,因此可以被称为in位置;而它的返回值是输出数据的地方,可以成为out位置

    interface MyClass<T> {
        fun method(param: T) : T {
            // 在这个函数中,传参的T是in位置,返回值的T是out位置
        }
    }

在Java中,假设Student类是Person类的子类,那么SimpleDate(Student)并不是SimpleDate(Person)的子类

在Kotlin中,泛型的协变的定义:假如定义了一个MyClass的泛型类,其实A是B的子类型,同时MyClass<A>又是MyClass<B>的子类型,那么就可以称在T这个泛型上是协变的

那么如何才能让MyClass<A>是MyClass<B>的子类型呢?如果一个泛型在其泛型的数据上是只读的,那么它是没有类型转换安全隐患的,而要实现这一点,则需要让MyClass类中的所有方法都不能接收T类型的参数,也就是说,T只能出现在out位置上,而不能出现在in位置上

class SimpleData<out T>(val data: T?) {
    fun get(): T? {
        return data
    }
    
    fun main() {
        val student = Student("kotlin", 19)
        val data = SimpleData<Student>(student)
        handleMyData(data)
        val studentData = data.get()
    }
    
    fun handleMyData(data: SimpleData<Person>) {
        val personData = data.get()
    }
}

open class Person {
    
}

class Student(val name: String, val age: Int) : Person() {
    
}

在SimpleData的泛型T前面加上了out关键字,这意味着T只能出现在out位置上,不能出现在in位置上,也意味着SimpleData在泛型T上是协变的

由于泛型T不能出现在in位置上,所以不能使用set()方法为data参数赋值;但如果确实需要在in位置上出现,那么可以使用val关键字修饰,或者用private来修饰,来表明该泛型T对于外部而言是只读的,不可修改的

在Kotlin中,Kotlin已经默认给许多内置的API加上了协变的声明,其中就包括了各种集合的类与接口

例如:在Kotlin中的List本身就是只读的,因此它天然支持协变;如果需要给List添加数据,那么需要使用MutableList才行

泛型的逆变

泛型的逆变与协变完全相反,假设定义一个MyClass的泛型类,其中A是B的子类型,同时MyClass<B>是MyClass<A>的子类型,那么可以称之为MyClass在T这个泛型上是逆变的

interface Transformer<in T> {
    fun transform(t: T): String
}

class SimpleData2 {
    fun main() {
        val trans = object : Transformer<Person> {
            override fun transform(t: Person): String {
                return "name = ${t.name}, age = ${t.age}"
            }
        }
        
        handleTransformer(trans)
    }
    
    fun handleTransformer(trans: Transformer<Student>) {
        val student = Student("Kotlin", 19)
        val result = trans.transform(student)
    }
}

open class Person {
    var name: String? = ""
    
    var age: Int? = 0
}

class Student(name: String, age: Int) : Person() {

}

Kotlin在提供协变和逆变的功能时,就已经把所有潜在的类型转换安全隐患全部考虑进去了,只要严格按照语法规则,让泛型在协变时只出现在out位置上,逆变时只出现在in位置上,就不会存在类型转换异常的情况了

注意:虽然@UnsafeVariance注解可以打破语法规则,但可能存在额外的风险,因此需要谨慎使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值