美文网首页
Kotlin之泛型的实化、协变、逆变

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

作者: code希必地 | 来源:发表于2020-10-28 14:27 被阅读0次

1、泛型的实化

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

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

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

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

调用getType()

fun kotlinTest() {
        val stringType:Class<String> = getType<String>()
        val intType:Class<Int> = getType<Int>()
        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 <reified T> openActivity(context: Context){
       val intent=Intent(context,T::class.java)
       context.startActivity(intent)
   }

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

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

调用就很简单了

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

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

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

报错信息如下:

openActivity(android.content.Context, kotlin.jvm.functions.Function1<? super android.content.Intent,kotlin.Unit>)' 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<Person>对象

fun handleData(person: List<Person>) {}

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

class SimpleData<T>() {

    private var t: T? = null

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

    fun get(): T? = t
}

创建handleData()函数接收SimpleData<Person>参数

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

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

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

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

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

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

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

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

3、泛型的逆变

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

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

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

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

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

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

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

相关文章

  • Kotlin泛型的高级特性(六)

    泛型的高级特性1、泛型实化2、泛型协变3、泛型逆变 泛型实化 在Java中(JDK1.5之后),泛型功能是通过泛型...

  • Kotlin学习 8 -- 泛型的高级特性

    本篇文章主要介绍以下几个知识点:对泛型进行实化泛型实化的应用泛型的协变泛型的逆变内容参考自第一行代码第3版 1. ...

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

    1、泛型的实化 Java中泛型是在JDK1.5引入的,是一个伪泛型,它是通过泛型擦除机制来实现的。泛型只存在编译时...

  • Kotlin学习笔记 - 泛型

    1. 基本用法 2. 型变 型变包括 协变、逆变、不变 三种: 协变:泛型类型与实参的继承关系相同 逆变:泛型类型...

  • Scala 通俗易懂 ---- 协变、逆变、不变

    协变、逆变、不变 Scala 语言中协变、逆变、不变是指拥有泛型的类型,在声明和赋值时的对应关系 协变:声明时泛型...

  • 泛型编程中的型变

    在泛型编程中,经常会提到型变。型变分为两种:协变与逆变。协变covariant表示与泛型参数T的变化相同,而逆变c...

  • Scala 泛型协变与泛型边界

    代码准备 泛型协变 泛型协变、逆变、不变是指拥有泛型的类在声明和赋值时的对应关系。 协变:声明时泛型是父类,赋值时...

  • Kotlin泛型与协变及逆变剖析

    Kotlin泛型与协变及逆变剖析 关于泛型的使用其实很简单,但是!!如文章开头所说,一直理解不了在Java框架中很...

  • Kotlin 泛型:协变、逆变

    1、Why?为什么需要泛型? 根本目的是在保证泛型类 类型安全的基础上,提高API的灵活性 2、How?如何保证类...

  • Java协变和逆变

    泛型的协变与逆变 协变与逆变用来描述类型转换(type transformation)后的继承关系,其定义如下:如...

网友评论

      本文标题:Kotlin之泛型的实化、协变、逆变

      本文链接:https://www.haomeiwen.com/subject/etsqvktx.html