前言
英词成群,非求雅态。固知义足矣,译徒增负也。何以弃译知义,初篇答之。
正文
刚才复习这个知识点,网上都说得很模糊,并且这个特性在很多种语言中都有,我便总结一下。为尽可能照顾广大读者,用Java和Kotlin(JVM)写了示例。示例很简单,如果某些语言特性不知道的话自己猜想一下便可以了,你猜的基本都是对的。
初衷
先从初衷讲起,我认为所有教学都该这样。Covariance,contravariance是为了便于传递相似参数(和泛型不同)而设计的。Java中并不支持,但能用wildcard(通配符)来类似实现。
先列出三个class,A为B的superclass,B为C的superclass。(superclass指父类,subclass指子类)
class A{ int a = 0; } class B extends A { int b = 1; } class C extends B { int c = 2; }
对于这样一个方法
void siteTest(List<B> list){...}
如果存在一个参数,其类型为List<C>或者List<A>, 它是传递不到siteTest这个方法中的,因为在siteTest中使用该参数时存在安全隐患(见下文)。但如果ide(开发工具)针对这种情况在写代码时加一些限制以确保安全,不就提高很多效率了吗。
Contravariance
先用wildcard在Java中类似实现,如下图。
如上,通过ide对list限制为只写,便安全了。
如果List在设计中就可以一并实现的话,便是contravariance,如下
//interface换成class也是可以的 interface List<? super T>{} //如果再限制T为A的subclass,便是 interface List<? super T extends A>{}
这样图中list的类型只要写为List<B>就可以了。当然这只是为了举例说明,Java的class或interface并不支持这样设计。不过很多其他语言都是支持的,如果用Kotlin来写的话便是
interface List<in T> //或者 interface List<in T : A>
设计成in这个标识是为了更好帮你理解所限制的使用范围。这时ide会限制在List的子类中,T只能出现在方法括号的内部,T型变量也只能为private。试想一下,这样是不是就限制为只写了。
上述只是为了举例,Kotlin中的List并没有这样设计。
此外,如果安全隐患可被自己手动排除呢。比如在写库的时候,自己明确知道会传进来哪些参数,可以使用哪些参数。如果list只可能为List<A>或者List<B>,那么使用list.get(0).a时是不是就安全了。(当然这是建立在已经确保list不为空,a已经初始化的情况下) Kotlin(JVM)为此提供了解决方法,对确保安全的参数,在其类型 T 前加一个注解UnsafeVariance,如下
class InSite <in T>(var value: @UnsafeVariance T)
此时value为private,但get,set方法为public。此外在方法中单独声明也是可以的,但这样就不能用@UnsafeVariance来解除限制了,如下:
fun inSiteTest(v:InSite<in B>){}
每种语言的解决方法不太一样,就不一一介绍了。
Covariance
先用wildcard在Java中类似实现,如下图。
此时ide会在outSiteTest中对 list 限制为只读,排除安全隐患。在其他语言中实现covariance类比之前的contravariance,把 in 改为 out 即可,以限制T只能出现在方法的括号外部,内含的T型变量还是只能为private。
至此,已经把covariance和contravariance介绍完毕,相信你已经领悟了。
不定期更新
另有三个微信公众号,前者是针对整个计算机领域的,后两者是针对安卓的,并会转载第一个公众号的每篇文章。公众号可以根据需要分开关注,更新得也会快一些,欢迎浏览。