企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
到这里很多读者可能会有疑惑,你说协变我还好理解,毕竟原来是父子,支持泛型协变后的泛型类也还是父子关系。但是反过来又是什么一个什么情况?比如Double是Number的子类型,反过来`Generic<Double>`却是`Generic<Number>`的父类型?那么到底有没有这种场景呢? 我们来思考一个问题,假设现在需要对一个`MutableList<Double>`进行排序,利用其sortWith方法,我们需要传入一个比较器,所以可以这么做: ``` val doubleComparator = Comparator<Double> { d1, d2-> d1.compareTo(d2) } val doubleList = mutableListOf(2.0, 3.0) doubleList.sortWith(doubleComparator) ``` 暂时来看,没有什么问题。但是现在我们又需要对`MutableList<Int>`、`MutableList<Long>`等进行排序,那么我们是不是又需要定义intComparator、longComparator等呢?现在看来这并不是一种好的解决方法。那么**试想一下可不可以定义一个比较器,给这些列表使用。我们知道,这些数字类有一个共同的父类Number,那么Number类型的比较器是否代替它的子类比较器**?比如: ``` val numberComparator = Comparator<Number> { n1, n2-> n1.toDouble().compareTo(n2.toDouble()) } val doubleList = mutableListOf(2.0, 3.0) doubleList.sortWith(numberComparator) val intList = mutableListOf(1,2) intList.sortWith(numberComparator) ``` 编译通过,验证了我们的猜想。那么为什么numberComparator可以代替doubleComparator、intComparator呢?我们来看一下**sortWith方法的定义**: ``` public fun <T> MutableList<T>.sortWith(comparator: Comparator<in T>): Unit { if (size > 1) java.util.Collections.sort(this, comparator) } ``` 这里我们又发现了一个**关键词in**,跟out一样,它也使泛型有了另一个特性,那就是**逆变**。简单来说,**假若类型A是类型B的子类型,那么`Generic<B>`反过来是`Generic<A>`的子类型**,所以我们就可以将一个numberComparator作为doubleComparator传入。那么将泛型参数声明为逆变会不会有什么限制呢? 前面我们说过,**用out关键字声明的泛型参数类型将不能作为方法的参数类型,但可以作为方法的返回值类型,而in刚好相反**。比如声明以下一个列表: ``` interface WirteableList<in T> { fun get(index: Int): T //Type parameter T is declared as 'in' but occurs in 'out' position in type T fun get(index: Int): Any //允许 fun add(t: T): Int //允许 } ``` 我们**不能将泛型参数类型当作方法返回值的类型,但是作为方法的参数类型没有任何限制**,其实从in这个关键词也可以看出,in就是入的意思,可以理解为消费内容,所以我们**可以将这个列表看作一个可写、可读功能受限的列表,获取的值只能为Any类型**。在Java中使用`<? super T>`可以达到相同效果。 到这里相信大家对泛型的变形,以及如何在Kotlin中使用协变与逆变有了大概的了解了。下面我们就着重来探讨一下如何简单地使用它们,并总结它们之间的差异。