💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
## 打破泛型不变 前面我们所讲的都是泛型的一些基本概念,比如为什么需要泛型,运行时泛型的状态等。下面我们将会了解泛型的一些高级特性,比如协变、逆变等,并学习Kotlin如何将之前在Java泛型中比较难以理解的概念进行更优雅的改造,变得更容易理解。 ### 为什么`List<String>`不能赋值给`List<Object>` 我们在前面已经提出了类似的问题,`List<Apple>`无法赋值给`List<Fruit>`,并接触了数组是协变,而List是不变的相关概念,而且用反证法说明了如果在Java支持直接声明泛型数组会出现什么问题。现在我们用同样的思维来看待这个问题,假如`List<String>`能赋值给`List<Object>`会出现什么情况。我们来看一个例子: ``` List<String> stringList = new ArrayList<String>(); List<Object> objList = stringList; //假设可以,编译报错 objList.add(Integer(1)); String str = stringList.get(0); //将会出错 ``` 我们发现,**在Java中如果允许`List<String>`赋值给`List<Object>`这种行为的话,那么它将会和数组支持泛型一样,不再保证类型安全,而Java设计师明确泛型最基本的条件就是保证类型安全,所以不支持这种行为**。但是到了Kotlin这里我们发现了一个奇怪的现象: ``` val stringList: List<String> = ArrayList<String>() val anyList: List<Any> = stringList //编译成功 ``` 在Kotlin中竟然能将`List<String>`赋值给`List<Any>`,不是说好的Kotlin和Java的泛型原理是一样的吗?怎么到了Kotlin中就变了?其实我们前面说的都没错,关键在于这两个List并不是同一种类型。我们分别来看一下两种List的定义: ``` public interface List<E> extends Collection<E> { ... } public interface List<out E> : Collection<E> { ... } ``` 虽然都叫List,也同样支持泛型,但是Kotlin的List定义的泛型参数前面多了一个out关键词,这个关键词就对这个List的特性起到了很大的作用。普通方式定义的泛型是不变的,简单来说就是不管类型A和类型B是什么关系,`Generic<A>`与`Generic<B>`(其中Generic代表泛型类)都没有任何关系。比如,在Java中String是Oject的子类型,但`List<String>`并不是`List<Object>`的子类型,在Kotlin中泛型的原理也是一样的。但是,Kotlin的List为什么允许`List<String>`赋值给`List<Any>`呢?