【Kotlin笔记】泛型的特性(实化、协变、逆变)

泛型的实化

  • Java对泛型功能是通过类型擦除机制来实现的。
  • JVM识别不出我们在代码中指定的泛型类型,泛型对于类型的约束只存在于编译时期。
    所以我们不能这样使用泛型
    fun <T> A()=T::class.java
    
  • Kotlin中提出了内联函数,让泛型实化成为了可能,使用内联函数会将定义了泛型的函数在编译的时候自动被替换到调用它的地方,这样就解决了泛型擦出的问题。

泛型实化的过程

  • 必须为内联函数,即使用inline修饰
  • 在声明泛型的地方加上reified
inline fun <reified T> A()=T::class.java
//实化后,我们就可使用T处理逻辑了

泛型的协变

定义:

  • 有一个MyClass< T>,如果B是A的子类。
  • MyClass< B>是MyClass< A>的子类。
  • 那么我们就可以称MyClass在T下是协变的。

通常来说我们定义一个A类,再定义一个B类让它继承于A类,成为A类的子类,那么一个方法接收A类,我们传入它的子类B是合理的。
但是一个方法接收MyClass< A>类的参数,我们传入MyClass< B>却是错误的。
下面举例说明为什么是错误的

//这里定义了一个A类声明为可继承,B、C类继承A类成为它的子类
open class A()
class B():A()
class C():A()
//一个声明了泛型的MyClass类,其中包含set、get方法
class MyClass<T>{
    private var data :T?=null
    fun set(t:T){
        data=t
    }
    fun get():T?{
        return data
    }
}
fun main() {
    val b=B()  
    val example=MyClass<B>()
    example.set(b)            //将B的实例并将它封装到MyClass当中
    handleSimpleExample(example)  //编译器会报错
    val bExample=example.get()
}
//这个方法传入一个MyClass参数参数,内部创建了一个C的实例,用它来替换MyClass中原来的数据
fun handleSimpleExample(example:MyClass<A>){
    val c=C()
    example.set(c)
}

我们来看下MyClass< B>作为参数传递给handleSimpleExample()时,编译器为什么会报错,我们假设这一步正常运行了,接下来我们调用example的get()方法获取Student数据时,里面确实一个Teacher实例,就会产生类型转换异常,所以本质上MyClass< B>不能说是MyClass< A>的子类。

那么有什么办法能让MyClass< B>成为MyClass< A>的子类呢?
上述问题的主要原因是我们进行了数据的设置,如果我们避免进行数据设置,就不会产生上面的问题了,所以我们只需要将T设为只读。
不能使用set()方法,所以我们使用构造函数将data传入,并且使用val关键字,保证只可读性,并且在泛型前面加上out。

class MyClass<out T>(val data : T?){
    fun get():T?{
        return data
    }
}

泛型的逆变

定义:

  • 有一个MyClass< T>,如果B是A的子类。
  • MyClass< A>是MyClass< B>的子类。
  • 那么我们就可以称MyClass在T下是逆变的。

逆变的定义恰恰和协变相反,下面来看看一个错误的例子,在实际情况下是会报错的。

首先我定义了一个带有泛型的接口,并且返回了它

interface Conversion<T>{
    fun conversion(t:T):T
}
fun main() {
    //通过匿名类实现接口
    val con =object :Conversion<A>{
        //通过conversion返回一个B实例,B为A的子类,所以这没问题
        override fun conversion(t: A): A {
            return B()
        }
    }
    
    handleConversion(con)  //出现错误
}
//这里定义了一个方法,传入一个Conversion参数并返回一个C对象
fun handleConversion(con :Conversion<C>){
    val c=C()
    val result=con.conversion(c)
}

handleConversion(con)这步出现错误的原因,是因为我们希望得到一个C对象,但是却返回了一个B对象,这里就会造成类型转换异常。
解决方法恰恰和协变相反,协变是设置数据造成的问题,而逆变是读取数据造成的问题,所以只需将它设置为不可读,在泛型前面加上in。

interface Conversion<in T>{
    fun conversion(t:T)

你可能感兴趣的:(Kotlin,kotlin,泛型)