泛型变体
当Kotlin类需要使用到declaration-site variance时,在Java代码中使用它们时有两个选择。让我们来讨论一下下面的类及其两个函数:
class Box<out T>(val value: T)
interface Base
class Derived : Base
fun boxDerived(value: Derived): Box<Derived> = Box(value)
fun unboxBase(box: Box<Base>): Base = box.value
将上述的代码转换成Java代码:
Box<Derived> boxDerived(Derived value) { ... }
Base unboxBase(Box<Base> box) { ... }
问题在于Kotlin中可以使用unboxBase(boxDerived("s")),这段代码并不能运行,我估计作者的意思是boxDerived()方法返回一个Box<Derived>,而unboxBase()是需要一个Box<Base>作为参数。但是因为Box类中的参数T在Java中是不变的,因此Box<Derived>并不是Box<Base>的子类。为了在Java中能够按照上述的方式进行调用,我们需要按照如下的方式定义unboxBase方法:
Base unboxBase(Box<? extends Base> box) { ... }
这里我们使用了Java的通配符类型(? extends Base)来模仿声明点协变,因为这是Java所拥有的。
为了让Kotlin API在Java中正常执行,我们生成了Box<? extends Super>来表示Box的协变定义或者Foo<? super Bar>来表示Foo的逆变定义,这个建议理解一下Java中的泛型extends,super用法。当它是一个返回值时,不会生成通配符,因为如果那样做就会在Java客户端处理它们。因此,上述的函数会被转换成如下的形式:
// return type - no wildcards
Box<Derived> boxDerived(Derived value) { ... }
// parameter - wildcards
Base unboxBase(Box<? extends Base> box) { ... }
需要注意的是:当参数类型是final的,没必要生成通配符,因此Box<String>总是Box<String>,不会随着访问的位置而变化。
如果我们需要在没有生成通配符的使用通配符,我们可以使用@JvmWildcard注解:
fun boxDerived(value: Derived): Box<@JvmWildcard Derived> = Box(value)
// is translated to
// Box<? extends Derived> boxDerived(Derived value) { ... }
相反,如果我们在生成了通配符的地方不想使用通配符,我们可以使用@JvmSuppressWildcards注解:
fun unboxBase(box: Box<@JvmSuppressWildcards Base>): Base = box.value
// is translated to
// Base unboxBase(Box<Base> box) { ... }
需要注意的是:@JvmSuppressWildcards不仅可以用在单独的类型参数,也可以用在整个声明上,比如函数和类,从而抑制所有的通配符生成。