Kotlin 中的泛型:协变与逆变
本文需要读者掌握的背景知识:
对 Kotlin/Java 泛型或 C++ 模板具有一定了解。
对 Java 字节码有一定认识。
前言
在学习 Kotlin
,阅读官方源码时,常遇到如下形式的代码:
根据对其他编程语言的经验,不难理解这里用到了模版/泛型的概念。但是,关键字 in
和 out
又是什么含义,出现在泛型类型占位符的前面有什么作用?
泛型(Generics)
什么是泛型
泛型的本质是参数化类型,即把类型当作参数。
以求数组最大值为例,
如果没有泛型,我们需要为每个数值类型写一个函数
显而易见,这种方式带来了很多重复的代码。经分析发现规律:上述三个函数,作用相同,都是比较数值的大小,仅传入和返回的参数类型不同。
如果能写成 fun getMaxElement(array: Array<T>): T? = array.maxByOrNull { it }
这样的代码,就好了。省去了很多重复且不必要的代码。泛型是为了解决这个问题的。
有了泛型后,对相同功能、但参数类型不同的函数,只需要写一个函数
对容器(如 List
、Array
)、类(Class
),也是同样的道理。
泛型如何实现
先来看个例子:现在有两个 List
,以及它们对应的反汇编代码(Kotlin Bytecode)
可以看到它们几乎毫无区别,那它们是怎么实现泛型的特性呢?
Kotlin 是一门在 JVM 上运行的静态类型编程语言,所以我们可以参考下 Java 关于泛型的实现。
通过搜索资料发现(参考资料 1),Java 是通过类型擦除(Type erasure)实现泛型的。
步骤:
编译产生字节码时,用 Object(针对没有约束的泛型)或者约束类型(针对有约束的泛型)替换泛型参数。
为了类型安全,必要时插入类型转换。
为保留泛型的多态性,生成 Bridge Methods。
可以看到,过程中未产生新的 Class,并且均是在编译期完成的,所以泛型的引入未增加运行时开销。
但为什么要这么做?
部分原因是 Java 的向后兼容。泛型在 Java 1.5 引入的,在它之前,要实现类似的功能,是通过强制类型转换,所以底层存储用的是 Object
对象。为了兼容这点,泛型未对底层存储数据类型进行改革,而是通过编译器检查类型、自动转换类型等技术实现。
举例:本节开头的例子中,只定义了两个分别存储 String
和 Int
的 List
,通过反编译查看字节码发现它们在对象创建时的指令是相同的,那访问元素时呢?
可以看出,从 List 中取出元素时仍是 Object
类型,随后编译器会插入类型转换指令,并继续进行操作。
泛型的优点
总结下,Kotlin/Java 实现的泛型具有如下优点:
消除重复代码。通过将类型参数化,大大减少重复代码。
类型安全。编译器在编译期完成类型的检测与转换。
泛型的变种(Variance)
协变(Covariant):out
定义:如果类型 A 是类型 B 的子类型,那么类型 Generics<A> 也是 Generics<B> 的子类型。
举例:String
是 Object
的子类型(显而易见),那么协变是指 List<String>
也是 List<Object>
的子类型。
Java 中为保证运行时类型安全,默认是没有这种关系的。但这种关系又很实用,例如元素拷贝:
如果没有协变,上述用法会报错。所幸 Java 支持这个,查看下 Collection.addAll()
方法签名:
Java 通过类型参数 ? extends E
实现协变关系。在上面的例子中,它表示 addAll
接受 Collection<E>
和 Collection<T>
作为函数的传入参数类型,其中 T
代表 E
的子类型(subtype)。Kotlin 中通过关键字 out
实现这种关系。
逆变(Contravariance):in
定义:如果类型 A 是类型 B 的子类型,那么逆变就是类型 Generics<B> 是 Generics<A> 的子类型。跟协变是反过来的。
举例:String
是 Object
的子类型,那么逆变是指 List<Object>
是指 List<String>
的子类型。
Java 中通过 <? super T>
实现这种关系。Kotlin 中通过关键字 in
实现这种关系。
应用场景
脱离应用场景的编程技巧是刷流氓的。
为什么需要逆变、协变?为什么有协变后,还需要逆变?
考虑一个泛型生产者 Producer<T>
,它只生产不消费,即它的方法中,没有任何一个方法的传入参数具有类型 T
,仅存在返回类型 T
的方法。这种情况下,使用 Producer<T>
引用一个 Producer<E>
的实例无疑是安全的,其中类型 E
是类型 T
的子类型。如果没有协变,则不支持这种操作。
与协变相反,逆变只能被消费,不能被生产。
总结
参考资料
版权声明: 本文为 InfoQ 作者【如浴春风】的原创文章。
原文链接:【http://xie.infoq.cn/article/6e5a800f57b36d36dbd70b672】。文章转载请联系作者。
评论