🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
上面我们看到了Kotlin中List的定义,它在泛型参数前面加了一个out关键词,我们在心里大概猜想,难道这个List的泛型参数是可变的?确实是这样的,**如果在定义的泛型类和泛型方法的泛型参数前面加上out关键词,说明这个泛型类及泛型方法是协变,简单来说类型A是类型B的子类型,那么`Generic<A>`也是`Generic<B>`的子类型**,比如在Kotlin中String是Any的子类型,那么`List<String>`也是`List<Any>`的子类型,所以`List<String>`可以赋值给`List<Any>`。但是我们上面说过,如果允许这种行为,将会出现类型不安全的问题。那么Kotlin是如何解决这个问题的?我们来看一个例子: ``` val stringList: List<String> = ArrayList<String>() stringList.add("kotlin") //编译报错,不允许 ``` 这又是什么情况,往一个List中插入一个对象竟然不允许,难道这个List只能看看?确实是这样的,**因为这个List支持协变,那么它将无法添加元素,只能从里面读取内容**。这点我们从List的源码也可以看出: ``` public interface List<out E> : Collection<E> { override val size: Int override fun isEmpty(): Boolean override fun contains(element: @UnsafeVariance E): Boolean override fun iterator(): Iterator<E> override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean public operator fun get(index: Int): E public fun indexOf(element: @UnsafeVariance E): Int public fun lastIndexOf(element: @UnsafeVariance E): Int public fun listIterator(): ListIterator<E> public fun listIterator(index: Int): ListIterator<E> public fun subList(fromIndex: Int, toIndex: Int): List<E> } ``` 我们发现,**List中本来就没有定义add方法,也没有remove及replace等方法,也就是说这个List一旦创建就不能再被修改,这便是将泛型声明为协变需要付出的代价**。那么为什么泛型协变会有这个限制呢?同样我们用反证法来看这个问题,如果允许向这个List插入新对象,会发生什么?我们来看一个例子: ``` val stringList: List<String> = ArrayList<String>() val anyList: List<Any> = stringList anyList.add(1) val str: String = anyList.get(0) //Int无法转换为String ``` 从上面的例子可以看出,**假如支持协变的List允许插入新对象,那么它就不再是类型安全的了,也就违背了泛型的初衷**。所以我们可以得出结论:**支持协变的List只可以读取,而不可以添加**。其实从out这个关键词也可以看出,out就是出的意思,可以理解为List是一个只读列表。在Java中也可以声明泛型协变,用通配符及泛型上界来实现协变:`<? extends Object>`,其中Object可以是任意类。比如在Java中声明一个协变的List: ``` public interface List <? extends T> { ...... } ``` 但泛型协变实现起来非常别扭,这也是Java泛型一直被诟病的原因。很庆幸,Kotlin改进了它,使我们能用简洁的方式来对泛型进行不同的声明。 另外需要注意的一点的是:**通常情况下,若一个泛型类`Generic<out T>`支持协变,那么它里面的方法的参数类型不能使用T类型,因为一个方法的参数不允许传入参数父类型的对象,因为那样可能导致错误。但在Kotlin中,你可以添加@UnsafeVariance注解来解除这个限制,比如上面List中的indexOf等方法**。 上面介绍了泛型不变和协变的两种情况,那么会不会出现第3种情况,比如类型A是类型B的子类型,但是`Generic<B>`反过来又是`Generic<A>`的子类型呢?