1.为什么List
通过反证法看这个问题,如果List
public static void main(String[] args) {
List strList = new ArrayList();
List
观察上面代码,如果Java中允许List
但是在 Kotlin中,可以发现:
fun main() {
val strList: List = ArrayList()
val anyList: List = strList
}
在Kotlin中尽然可以将List
Java:
public interface List extends Collection {
... ...
}
Kotlin:
public interface List : Collection {
......
}
两个List都支持泛型,但是Kotlin的List定义的泛型参数前面多了一个out关键字。
其实对于普通方式定义的泛型对于Java和Kotlin都是不变的,就是不管类型A和类型B是什么关系,Generic与Generic(Generic代表泛型类)都没有任何关系。比如在Java中String是Object的子类型,但是List
一个支持协变的List
Kotlin中的List,其泛型参数前面多了一个out关键字。如果在定义的泛型类和泛型方法的泛型参数前面加上out关键字,说明这个泛型类及泛型方法是协变的,简单来说就是类型A是类型B的子类型,那么Generic也是Generic的子类型。比如在Kotlin中String是Any的子类型,那么List
如果允许这种行为,将出现类型不安全的问题,那么Kotlin是如何解决这个问题的呢?
因为是一个支持协变的List,所以它无法添加元素,只能从里面读取内容。
下面代码会报错
fun main() {
val strList: List = ArrayList()
strList.add("one")//会报错,此时没有add方法
}
观察下面List的源码,发现确实没有add方法。
public interface List : Collection {
// Query Operations
override val size: Int
override fun isEmpty(): Boolean
override fun contains(element: @UnsafeVariance E): Boolean
override fun iterator(): Iterator
// Bulk Operations
override fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
// Positional Access Operations
/**
* Returns the element at the specified index in the list.
*/
public operator fun get(index: Int): E
// Search Operations
/**
* Returns the index of the first occurrence of the specified element in the list, or -1 if the specified
* element is not contained in the list.
*/
public fun indexOf(element: @UnsafeVariance E): Int
/**
* Returns the index of the last occurrence of the specified element in the list, or -1 if the specified
* element is not contained in the list.
*/
public fun lastIndexOf(element: @UnsafeVariance E): Int
// List Iterators
/**
* Returns a list iterator over the elements in this list (in proper sequence).
*/
public fun listIterator(): ListIterator
/**
* Returns a list iterator over the elements in this list (in proper sequence), starting at the specified [index].
*/
public fun listIterator(index: Int): ListIterator
// View
/**
* Returns a view of the portion of this list between the specified [fromIndex] (inclusive) and [toIndex] (exclusive).
* The returned list is backed by this list, so non-structural changes in the returned list are reflected in this list, and vice-versa.
*
* Structural changes in the base list make the behavior of the view undefined.
*/
public fun subList(fromIndex: Int, toIndex: Int): List
}
可以发现上面的List中本来就没有定义add方法,也没有remove以及replace方法。也就是说这个List一旦创建就不能再被修改。
其实可以通过反证法看这个问题:
假如支持协变的List允许插入新对象,那么它就不再是类型安全的了,也就违背了泛型的初衷。
所有结论是:支持协变的List只可以读取,而不可以添加。从out关键字也可以看出是出的意思,所以List是一个只读列表。
注意一点:
通常情况下,若一个泛型类Generic
一个支持逆变的Comparator
考虑一种情况:比如Double是Number的子类型,反过来Generic
假设现在要对一个MutableList
fun main() {
val doubleComparator = Comparator { d1, d2 ->
d1.compareTo(d2)
}
val doubleList = mutableListOf(2.0, 1.0, 3.0)
doubleList.sortWith(doubleComparator)
doubleList.forEach {
println(it)
}
}
1.0
2.0
3.0
在上面代码的基础上需要对MutableList
val numberComparator = Comparator { d1, d2 ->
d1.toDouble().compareTo(d2.toDouble())
}
val doubleList = mutableListOf(2.0, 1.0, 3.0)
doubleList.sortWith(numberComparator)
doubleList.forEach {
println(it)
}
val intList = mutableListOf(2, 1, 3)
intList.sortWith(numberComparator)
intList.forEach {
println(it)
}
1.0
2.0
3.0
1
2
3
expect fun MutableList.sortWith(comparator: Comparator): Unit
in关键字使泛型拥有了另一个特性,那就是逆变。简单来说,若类型A是类型B的子类型,那么Generic反过来是Generic的子类型。
将泛型参数设置为逆变有什么限制呢?
用in关键字声明的泛型参数类型可以作为方法的参数类型,但是不能作为方法的返回值类型。
例子:
interface WritableList {
fun add(t: T): Int //允许
fun get(index: Int): T //不允许,这句代码的写法是错误的。
}
协变和逆变
in 和out是一个对立面,in 代表泛型参数类型逆变,out 代表泛型参数类型协变。从字面上理解就是,in代表输入,out代表输出。但它们同时与泛型不变相互对立,统称为型变。
interface PayList : Collection {}
上述代码是在声明处型变,另外也可以在使用处型变(sortWith方法就是使用处型变)。
下面实现一个copy数组的需求?
实现Double数组的拷贝。
fun payCopy(dest: Array, src: Array) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed() { index, value ->
dest[index] = src[index]
}
}
}
fun main() {
val dest = arrayOfNulls(3)
val src = arrayOf(1.0, 2.0, 3.0)
payCopy(dest, src)
dest.forEach {
println(it)
}
}
1.0
2.0
3.0
进一步抽象,使其可以适应任意类型的拷贝。
fun payCopyCommon(dest: Array, src: Array) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed() { index, value ->
dest[index] = src[index]
}
}
}
但是,上面的方法还是有局限的,就是使用拷贝方法必须是同一种类型,那么如果希望将Array
in实现版本
fun payCopyIn(dest: Array, src: Array) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed() { index, value ->
dest[index] = src[index]
}
}
}
val destNumber = arrayOfNulls(3)
val srcDouble = arrayOf(1.0, 2.0, 3.0)
payCopyIn(destNumber, srcDouble)
destNumber.forEach {
println(it)
}
1.0
2.0
3.0
out版本
fun payCopyOut(dest: Array, src: Array) {
if (dest.size < src.size) {
throw IndexOutOfBoundsException()
} else {
src.forEachIndexed() { index, value ->
dest[index] = src[index]
}
}
}
val destNumber = arrayOfNulls(3)
val srcDouble = arrayOf(1.0, 2.0, 3.0)
payCopyOut(destNumber, srcDouble)
destNumber.forEach {
println(it)
}
观察in版本与out版本的区别?
in是声明在dest数组上的,out是声明在src数组上的,所以dest可以接收T类型的父类型的Array,out可以接收T类型的子类型的Array。这里的T需要到编译时才会确定。
in版本,T是Double类型,所以dest可以接收Double类型的父类型 Array,比如Array
out版本,T是Number类型,所以src可以接收Number类型的子类型Array,比如Array
Kotlin 与Java型变的比较
协变 |
逆变 |
不变 |
|
Kotlin |
|
|
|
Java |
extends T>只能作为消费者,只能读取不能添加。 |
super T>只能作为生产者,只能添加,读取受限制 |
|
参考 Kotlin核心编程