Kotlin泛型的高级特性(六)

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

可以看出来O调用了一个带有泛型的内联函数,O调用了getSome。在代码编译后,O函数中的代码将可以获得泛型的实际类型,也就是注释里写的。这意味着在Kotlin中泛型是可以得到实化的。
基于上述,我们可以思考一下如何获取对应的实化类型,举例:
fun getO() { val type1 = getenericType() val type2 = getenericType() println("result:$type1") println("result:$type2") }inline fun getenericType() = T::class.javalog-> result:class java.lang.String result:class java.lang.Integer

可以看到讲泛型类型定义String,就能获取到String的类型,这就是泛型的实化。
reified这个函数,是我在这样写了之后,编译必须要加的,否则不通过。我又查阅了一些资料, 由此得到结论,获取泛型的实化必须被reified和inline同时修饰。
结论
泛型的实化的前置条件 : 必须被reified和inline同时修饰。
既然如此我自然想在项目中应用,举例:
inline fun startActivity(context: Context?) { val intent = Intent(context, T::class.java) context?.startActivity(intent) }inline fun 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(activity) } llAboutUs -> { startActivity(activity) { putExtra("name", "name") putExtra("name2", "name2") } } } }

  • 泛型协变
假设某个方法接收的是Persion类型,但是你传入的是个Student类型。在Java中是不允许这么做,因为Student类型不能成为Persion类型,否则可能存在类型转换的安全隐患。说白了,程序会报 :类转换异常的错误。
而在Kotlin中,假设MyClass的泛型类,其中A是B的子类型,同时MyClass是MyClass的子类型,那么我们就可以称MyClass在T这个泛型上是“协变”的。
如果一个泛型类在其泛型类型的数据上是只读的话,那么它是没有类型转换安全隐患的,而要实现这一点,则需要让MyClass类中所有的方法都不能接收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(val aa: T?) { fun getData(): T? { return aa } }

SimpleData的泛型out T是只读,而参数aa是传入的T,不对其具有修改。那么:
fun main() { val student = Student("name", "age") val simpleData = https://www.it610.com/article/SimpleData(student) handleSimpleData(simpleData) val data = simpleData.getData() println("type:$data") }fun handleSimpleData(data: SimpleData) { //这里获取的是一个Persion类型, //但是因为Persion是Student的父类,向上转型是完全安全的。 //如果某个方法接收的是List类型,而传入的是List类型,在Java中是不允许这么做的。 val data1 = data.getData() println("type:$data1") }

这就是泛型的协变。
其实Kotlin已经默认给许多内置的API加上了协变声明。其实就包括了各种集合。Kotlin中的List本身就是只读的,如果你想要修改的话,需要使用MutableList才行!!!也就意味着它天然就是可以协变的。
List部份源码:
public interface List : Collection { // Query Operationsoverride val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator// 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是MyClass的子类型。逆变就是将此处返过来。假设MyClass的泛型类,其中A是B的子类型,同时MyClass是MyClass的子类型,那么我们就可以称MyClass在T这个泛型上是“协变”的。
那么已知A是B的子类型,怎么让MyClass过来成为MyClass的子类型呢。举例如下:
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 { fun transForms(t: T): String }fun main() { val oo = (object : TransForms { override fun transForms(t: Persion): String { return "value: ${t.name}" + "${t.age}" } }) setValue(oo) }fun setValue(trans: TransForms) { val student = Student("zhangsan", "12") trans.transForms(student) }

从代码的角度来说,Student是Persion的子类。而逆变就在接口里:in
在泛型T声明上加了个in,意味着T现在只能出现在in的位置上,而不能出现在out的位置上。逆变的用法大概就这样了。
【Kotlin泛型的高级特性(六)】让我们继续深入,为什么逆变的时候泛型T不能出现在out上呢?我们假设它可以。
interface TransForms { fun transForms(name: String, age: String): @UnsafeVariance T }

为了让编译通过,@UnsafeVariance修饰。
fun main() { val oo = (object : TransForms { override fun transForms(name: String, age: String): Persion { return Teacher(name, age) }}) setValue(oo) }fun setValue(trans: TransForms) { //想要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 { /** * 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实现了让两个Person对象比较大小的逻辑。那么用这段逻辑去比较两个Student对象的大小也一定是成立的。因此让Comparable成为Comparable的子类型合情合理。
这就是逆变。

    推荐阅读