Kotlin之泛型的实化、协变、逆变

1、泛型的实化

Java中泛型是在JDK1.5引入的,是一个伪泛型,它是通过泛型擦除机制来实现的。泛型只存在编译时期,运行时泛型就会被擦除,所以我们无法在运行时获取泛型的类型信息。
Kotlin最终也会编译生成和Java相同规格的class文件,所以Kotlin中的泛型也会被擦除,所以我们无法使用a is T 和 T::class.java但是Kotlin提供了一个内联函数,在编译时就会将内联函数的代码替换到实际调用的地方,所以对于内联函数来说是不存在泛型的擦除的。
所以我们是可以将内联函数中的泛型进行实化的,泛型实化的前提有2个:

  • 1、必须泛型函数且是内联函数
  • 2、声明泛型的地方加上关键字reified修饰(reified只能用于内联泛型函数)
    具体代码如下:
inline fun  getType() {}

通过泛型实化可以获取泛型类型的功能,创建Reified.kt,并在其中定义一个顶层函数

inline fun  getType() = T::class.java

调用getType()

fun kotlinTest() {
        val stringType:Class = getType()
        val intType:Class = getType()
        println("stringType:${stringType.name} , intType:${intType.name}")
    }

打印结果如下:

stringType:java.lang.String , intType:java.lang.Integer

可以看到我们在运行时获取到了泛型的类型。

泛型实化的使用场景

先看下打开Activity的代码

 fun openActivity(){
        val intent=Intent(this,MainActivity::class.java)
        startActivity(intent)
    }

下面看下如何通过泛型的实化来实现这个功能

inline fun  openActivity(context: Context){
       val intent=Intent(context,T::class.java)
       context.startActivity(intent)
   }

但是问题又来了,怎么使用Intent进行数据的传输呢?其实很简单,我们可以使用高阶函数来实现,具体传输的数据由调用者来完成。

inline fun  openActivity(context: Context, block: Intent.() -> Unit) {
        val intent = Intent(context, T::class.java)
        intent.block()
        context.startActivity(intent)
    }

调用就很简单了

openActivity(this) {
                putExtra("name", "LiLei")
                putExtra("age", 12)
            }

这样我们就完成了打开页面通用的方法,可以把openActivity()函数定义成顶层函数,这样我们就可以在任意位置调用了,不过泛型的实化是无法在Java中调用的。下面看下在Java中如何调用openActivity()

 ReifiedKt.openActivity(context, new Function1() {
            @Override
            public Unit invoke(Intent intent) {
                intent.putExtra("name", "LiLei");
                intent.putExtra("age", 12);
                return null;
            }
        });

报错信息如下:

openActivity(android.content.Context, kotlin.jvm.functions.Function1)' has private access in 'com.example.abu.serviceproject.ReifiedKt

可以看到在Java中无权访问该方法的。

2、泛型的协变

一个泛型类或泛型接口中的方法,方法的参数列表是接收数据的,称为in位置,方法的返回值是返回数据的,称为out位置。
在讲解泛型的协变之前,先看个例子:创建一个Person类、Student类、Teacher类,让Student和Teacher继承Person类。

open class Person(var name: String, var age: Int)  {}
class Student(name: String, age: Int) : Person(name, age) 
class Teacher(name: String, age: Int) : Person(name, age)

创建一个方法,接收的参数是Person对象

fun handleData(person: Person){}

该方法接收Person对象,那么能不能向其中传入Student对象或Teacher对象?
Student和Teacher都是Person的子类,所以是肯定可以的。下面我们修改下handleData()接收的参数为List对象

fun handleData(person: List) {}

那么handleData()能接收List/List吗?
在Java中是不允许的,List/List并不是 List的子类,否则存在类型转换异常的隐患的。下面通过例子来解析下原因:
创建泛型类SimpleData

class SimpleData() {

    private var t: T? = null

    fun set(t: T) {
        this.t = t
    }

    fun get(): T? = t
}

创建handleData()函数接收SimpleData参数

fun test() {
        val stu = Student("张", 12)
        val stuData = SimpleData()
        stuData.set(stu)
        handleSimpleData(stuData) //编译失败
        val result: Student? = stuData.get()
    }

    fun handleSimpleData(data: SimpleData) {
        val teacher = Teacher("李", 40)
        data.set(teacher)
    }

由于SimpleData并不是SimpleData子类,所以handleSimpleData(stuData)肯定是编译失败的,这里假设编译通过,那么在handleSimpleData()中创建Teacher对象并通过data.set(teacher)替换Student对象,然后通过stuData.get()获取的是Student对象,实际上返回的是Teacher对象,这就造成了类型转换异常。所以这种写法是不合法的。
如果不允许修改SimpleData中的数据,是不是就能解决类型转换异常的问题了。
Kotlin中提供了out关键字来保证泛型只能定义在返回值的位置,不能定义在接收参数的位置,使用out修改SimpleData类,代码如下:

class SimpleData(val t: T) {
    fun get(): T = t
}

不是说T不能出现在接收参数的位置吗?为什么能出现在构造函数中,这是因为val修饰的t是不允许修改,这也是符合安全规范的。同理,我们也可以使用private var t:T来代替val t:T
此时handleSimpleData(data: SimpleData)中就能接收SimpleData

fun test() {
    val stu = Student("张", 12)
    val stuData = SimpleData(stu)
    handleSimpleData(stuData) //编译成功
    val student: Student = stuData.get()
    Log.e(tag,"name is ${student.name} , age is ${student.age}。")
}

fun handleSimpleData(data: SimpleData) {
    val person = data.get()
    person.name = "李"
    person.age = 40
}

handleSimpleData(data: SimpleData)中就能接收SimpleData说明SimpleData就是SimpleData的子类,而Student又是Person的子类,这就是Kotlin中泛型的协变。

3、泛型的逆变

定义一个 MyClass 的泛型类,其中 A 是 B 的子类型,同时 MyClass 又是 MyClass 的子类型,就称 MyClass 在 T 这个泛型上逆变的。
下面举个例子来看下:

interface Transform {
    fun transform(t: T): String
}

fun main() {
    val tranform = object : Transform {
        override fun transform(t: Person): String {
            return "name is ${t.name}, age is ${t.age}"
        }
    }
    transformData(tranform)//编译失败
}

fun transformData(transform: Transform) {
    val student = Student("张", 12)
    transform.transform(student)
}

上面代码是无法正常的编译的,我们可以使用逆变来解决上面问题。修改Transform接口

interface Transform {
    fun transform(t: T): String
}

只要加上关键字in就可以实现泛型的逆变了,Transform也就变成了Transform的子类了。

你可能感兴趣的:(Kotlin之泛型的实化、协变、逆变)