Kotlin泛型的高级特性(六)

泛型的高级特性
1、泛型实化
2、泛型协变
3、泛型逆变


泛型实化


  在Java中(JDK1.5之后),泛型功能是通过泛型擦除来实现的。什么意思呢? 就是在对泛型的约束只是在编译阶段,运行的时候的JVM是识别不出来在代码中指定的类型的。 比如说List<String>,编译阶段限制了就是String,但在运行的时候JVM并不知道它本身只打算包含 “哪种类型”,只能识别它是个List。
  Kotlin也是这样,然而不同的是Kotlin提供了内联函数的概念。内联函数的意思就是,在编译的时候自动将内联函数修饰的代码替换到调用它的地方,这样的话就不会有泛型擦除了,因为代码在编译之后会使用实际的类型来替换内联函数中泛型声明。举个例子说明:

fun getO() {
    getSome<String>()
}

inline fun <T> getSome() {
    //dosomething with T 
}

上面的调用,经过实际替换是:
fun getO() {
     //dosomething with String
}


可以看出来O调用了一个带有泛型的内联函数,O调用了getSome。在代码编译后,O函数中的代码将可以获得泛型的实际类型,也就是注释里写的。这意味着在Kotlin中泛型是可以得到实化的。基于上述,我们可以思考一下如何获取对应的实化类型,举例:

fun getO() {
    val type1 = getenericType<String>()
    val type2 = getenericType<Int>()
    println("result:$type1")
    println("result:$type2")
}

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

log->
result:class java.lang.String
result:class java.lang.Integer


可以看到讲泛型类型定义String,就能获取到String的类型,这就是泛型的实化。
reified这个函数,是我在这样写了之后,编译必须要加的,否则不通过。我又查阅了一些资料,  由此得到结论,获取泛型的实化必须被`reified和inline `同时修饰。

结论
泛型的实化的前置条件 : 必须被`reified和inline `同时修饰。

既然如此我自然想在项目中应用,举例:

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

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

override fun onClick(v: View?) {
        when (v) {
            llOfflineMap -> {
                startActivity<OfflineMapActivity>(activity)
            }
            llAboutUs -> {
                startActivity<AboutActivity>(activity) {
                    putExtra("name", "name")
                    putExtra("name2", "name2")
                }
            }
        }
    }

 

泛型协变


  假设某个方法接收的是Persion类型,但是你传入的是个Student类型。在Java中是不允许这么做,因为Student类型不能成为Persion类型,否则可能存在类型转换的安全隐患。说白了,程序会报 :类转换异常的错误。
  而在Kotlin中,假设MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<A>是MyClass<B>的子类型,那么我们就可以称MyClass在T这个泛型上是“协变”的。
  如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的,而要实现这一点,则需要让MyClass<T>类中所有的方法都不能接收T类型的参数,换句话说就是。T只能出现在out位置上,而不能出现在in位置上。举例代码:

open class Persion(val name: String, val age: String)
class Student(val sname: String, val sage: String) : Persion(sname, sage)


student是Persion的子类。

class SimpleData<out T>(val aa: T?) {
    fun getData(): T? {
        return aa
    }
}


SimpleData的泛型out T是只读,而参数aa是传入的T,不对其具有修改。那么:

fun main() {
    val student = Student("name", "age")
    val simpleData = SimpleData<Student>(student)
    handleSimpleData(simpleData)
    val data = simpleData.getData()
    println("type:$data")
}

fun handleSimpleData(data: SimpleData<Persion>) {
    //这里获取的是一个Persion类型,
    //但是因为Persion是Student的父类,向上转型是完全安全的。
    //如果某个方法接收的是List<Persion>类型,而传入的是List<Student>类型,在Java中是不允许这么做的。
    val data1 = data.getData()
    println("type:$data1")
}


这就是泛型的协变。其实Kotlin已经默认给许多内置的API加上了协变声明。其实就包括了各种集合。Kotlin中的List本身就是只读的,如果你想要修改的话,需要使用MutableList才行!!!也就意味着它天然就是可以协变的。
List部份源码:

public interface List<out E> : Collection<E> {
    // Query Operations

    override val size: Int
    override fun isEmpty(): Boolean
    override fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>

    // Bulk Operations
    override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}


可以看到List具有协变。
contains这个参数,可以看到E出现在了in位置上,实际contains只是为了判断当前集合中是否包含从参数中传入的这个元素,不会修改当前集合中的内容,因此这种操作实质上又是安全的。 但编译器不支持,为了让编译器通过加了@UnsafeVariance 注解。也就是说你想让它在in上,加@UnsafeVariance 注解编译器就不管,但这滥用这种功能导致的类型转换异常,要自己承担咯~ 


结论:
1、协变必须满足 接收参数与传入参数之间是父子关系。
2、协变的T必须是out位,如果想在in加@UnsafeVariance,但这种也是不涉及修改内容的。

 

泛型逆变


  依据刚才的协变来说,直观的角度就是 ,MyClass<A>是MyClass<B>的子类型。逆变就是将此处返过来。假设MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<B>是MyClass<A>的子类型,那么我们就可以称MyClass在T这个泛型上是“协变”的。
   那么已知A是B的子类型,怎么让MyClass<B>过来成为MyClass<A>的子类型呢。举例如下:

open class Persion(val name: String, val age: String)
class Student(val sname: String, val sage: String) : Persion(sname, sage)
class Teacher(val tname: String, val tage: String) : Persion(tname, tage)


定义AB类型

interface TransForms<in T> {
    fun transForms(t: T): String
}

fun main() {
    val oo = (object : TransForms<Persion> {
        override fun transForms(t: Persion): String {
            return "value: ${t.name}" + "${t.age}"
        }
    })
    setValue(oo)
}

fun setValue(trans: TransForms<Student>) {
    val student = Student("zhangsan", "12")
    trans.transForms(student)
}


从代码的角度来说,Student是Persion的子类。而逆变就在接口里:in
在泛型T声明上加了个in,意味着T现在只能出现在in的位置上,而不能出现在out的位置上。逆变的用法大概就这样了。

让我们继续深入,为什么逆变的时候泛型T不能出现在out上呢?我们假设它可以。

interface TransForms<in T> {
    fun transForms(name: String, age: String): @UnsafeVariance T
}


为了让编译通过,@UnsafeVariance修饰。

fun main() {
    val oo = (object : TransForms<Persion> {
        override fun transForms(name: String, age: String): Persion {
            return Teacher(name, age)
        }

    })
    setValue(oo)
}

fun setValue(trans: TransForms<Student>) {
    //想要student的返回值
    val transForms = trans.transForms("zhangsan", "12")
}


上述代码就是典型的违反逆变规则而造成类型转换异常的例子。
在setValue方法中,我们期望得到的是一个Student的对象的返回,然而实际上
transForms方法返回的是一个Teacher对象。因此这里会造成类型转换异常的错误。

Exception in thread "main" java.lang.ClassCastException: com.hdsx.guangxihighway.Teacher cannot be cast to com.hdsx.guangxihighway.Student
    at com.hdsx.guangxihighway.TestKt.setValue(Test.kt:20)
    at com.hdsx.guangxihighway.TestKt.main(Test.kt:15)
    at com.hdsx.guangxihighway.TestKt.main(Test.kt)


也就是说Kotlin在提供协变和逆变的时候,就已经把各种潜在的类转换安全隐患考虑进去了,只要严格按照其语法规则。就不会存在类型转换异常的情况。虽然@UnsafeVariance可以打破,但是也承担着额外的风险。

逆变的典型例子:

public interface Comparable<in T> {
    /**
     * Compares this object with the specified object for order. Returns zero if this object is equal
     * to the specified [other] object, a negative number if it's less than [other], or a positive number
     * if it's greater than [other].
     */
    public operator fun compareTo(other: T): Int
}


为什么这个泛型上是逆变的呢,因为compareTo方法用于实现具体的比较逻辑。如果我们使用Comparable<Persion>实现了让两个Person对象比较大小的逻辑。那么用这段逻辑去比较两个Student对象的大小也一定是成立的。因此让Comparable<Persion>成为Comparable<Student>的子类型合情合理。

这就是逆变。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值