前言
英词成群,非舞文弄墨。固知义足矣,译徒增负也。何以弃译知义,顶篇答之。
正文
刚才复习这个知识点,中网、外网上都说得很模糊。并且这个特性在很多种语言中都有,我便总结一下。为尽可能照顾广大读者,用 Java 和 Kotlin 写了示例。示例很简单,如果某些语言特性不知道的话自己猜想一下便可以了,你猜的基本都是对的。
初衷
先从初衷讲起,我认为所有教学都该这样。
先列出三个 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(开发工具) 便禁止了。但如果 IDE 针对这种情况在写代码时加一些限制以确保安全,不就提高很多效率了吗,此即设计 covariance, contravariance的初衷。
Covariance
先用 wildcard (通配符) 在 Java 中类似实现,如下图。
如上,将类型为 List<C> 的 cs 传递给方法 outSiteTest,IDE 会自动对 list 限制为只读,这样便杜绝了安全隐患。
如果在设计 List 时就一并实现的话,便是 covariance,如下
//interface 换成 class 也是可以的
interface List<? extends T>{}
//如果再限制 T 为 A 的 subclass,便是
interface List<? extends T extends A>{}
这样在方法 outSiteTest 中将 List<? extends B> 写为 List<B> 就可以了。当然这只是为了举例说明,Java 的 class 或 interface 并不支持这样设计。不过很多其他语言都是支持的,如果用 Kotlin 来写的话便是这样:
interface List<out T>
//或者
interface List<out T : A>
一般其他语言也都会在 covariance 中采用 out,这样设计是为了帮你更好理解所限制的使用范围。如下所示,这时 IDE 会限制在 List 的子类中,全局 T 型变量只能为 private,对于非 private 的方法,T 只能出现在其括号外部(out)。如此,T 型变量的只读是不是就实现了。
此外,如果安全隐患可被自己手动排除呢。比如在写库的时候,自己明确知道会传进来哪些参数,可以使用哪些参数。在图中的例子里,cs 经 outSiteTest 使用后,如果在其他地方不对 cs 那个位置上的 element(元素) 取 c,是不是就安全了。Kotlin 为此提供了解决方法,对确保安全的参数不再限制,只要在其类型 T 前加一个注解 UnsafeVariance 就可以了,如下
class OutSite <out T>(var value: @UnsafeVariance T)
此时 value 为 private,但方法 getValue,setValue 均为 public。此外在方法中单独声明也是可以的,但这样就不能用 @UnsafeVariance 来解除限制了,如下:
fun outSiteTest(v:InSite<out B>){}
每种语言的解决方法不太一样,也可能并不支持解决,就不一一介绍了。
Contravariance
先用 wildcard 在 Java 中类似实现,如下图。
此时 IDE 会在 inSiteTest 中对 list 限制为只写,排除安全隐患。在其他语言中实现 contravariance 类比之前的 covariance,把 out 改为 in 即可。如下所示, 以限制 T 只能出现在 public 的方法括号内部(in),内含的全局 T 型变量同样只能为 private,实现只写。
至此,已经把 covariance 和 contravariance 介绍完毕,相信你已经领悟了。