Kotlin没有自己的集合库,完全依赖Java标准库中的集合类,并通过扩展函数增加特性来增强集合。意味着Kotlin与Java交互式,永远不需要包装或者转换这些集合对象,大大增强与Java的互操作性。
Kotlin与Java最大的不同之一就是:Kotlin将集合分为只读集合和可变集合。这种区别源自最基础的集合接口:kotlin.collections.Collection。该接口可以对集合进行一些基本操作,但无任何添加和移除元素的方法。
只有实现 kotlin.collections.MutableCollection 接口才可以修改集合的数据。MutableCollection 接口继承自 Collection,并提供添加、移除和清空集合元素的方法。当一个函数接收 Collection,而不是 MutableCollection,即意味着函数不对集合做修改操作。
可变集合一般都带有 “Mutable” 前缀修饰,意味着能对集合中的元素进行修改。 Iterable<T> 定义了迭代元素的操作, Collection 继承自 Iterable<T> 接口,从而具有对集合迭代的能力。
Kotlin中创建集合一般都是通过 Collection.kt 中的顶层函数进行创建。具体方法如下:
| 集合类型 | 只读 | 可变 |
|---|---|---|
| List | listOf | mutableList、arrayListOf |
| Set | setOf | mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf |
| Map | mapOf | mutableMapOf、hashMapOf、linkeMapOf、sortedMapOf |
像 arrayListOf 这些指明集合类型的顶层函数,创建时都是对应着Java相应类型的集合。为了弄清楚 Kotlin 的生成的只读集合(listOf、setOf 和 mapOf)与可变集合(mutableList、mutableSetOf 和 mutableMapOf)生成的是什么Java类型集合,做了一个小实验:
#daqiJava.java
public static void collectionsType(Collection collection){
System.out.println(collection.getClass().getName());
}
public static void mapType(Map map){
System.out.println(map.getClass().getName());
}
复制代码
#daqiKotlin.kt
fun main(args: Array<String>) {
val list = listOf("")
val set = setOf("")
val map = mapOf("" to 1)
val mutableList = mutableListOf("")
val mutableSet = mutableSetOf("")
val mutableMap = mutableMapOf("" to 1)
println("只读集合类型:")
collectionsType(list)
collectionsType(set)
mapType(map)
println("可变集合类型:")
collectionsType(mutableList)
collectionsType(mutableSet)
mapType(mutableMap)
}
复制代码
结果:
可以得出只读集合(listOf、setOf 和 mapOf)与可变集合(mutableList、mutableSetOf 和 mutableMapOf)对应Java集合的关系表:
| 方法 | Java类型 |
|---|---|
| listOf | java.util.Collections$SingletonList |
| setOf | java.util.Collections$SingletonSet |
| mapOf | java.util.Collections$SingletonMap |
| mutableList | java.util.ArrayList |
| mutableSetOf | java.util.LinkedHashSet |
| mutableMapOf | java.util.LinkedHashMap |
只读集合类型是型变的。当类 Rectangle 继承自 Shape,则可以在需要 List<Shape> 的任何地方使用 List<Rectangle>。 因为集合类型与元素类型具有相同的子类型关系。 Map在值类型上是型变的,但在键类型上不是。
可变集合不是型变的。 MutableList 是 MutableList 的子类型,当你插入其他 Shape 的继承者(例如,Circle),从而违反了它的 Rectangle 类型参数。
对于任何类型,都可以对其声明为可空类型,集合也不例外。你可以将集合元素的类型设置为可空,也可以将集合本身设置为可空,需要清楚是集合的元素可空还是集合本身可空。
学习 Kotlin 的时候,常常被告知 Kotlin 直接使用的是原生 Java 集合,抱着探究真相的心态,点进了创建集合的顶层方法 mutableListOf() 。
#Collections.kt
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
if (elements.size == 0)
ArrayList()
else
ArrayList(ArrayAsCollection(elements, isVarargs = true))
复制代码
在源码中看到了熟悉的 ArrayList ,但你以为这就结束了吗?就是使用Java的ArrayList嘛?继续点进ArrayList,发现是一个Kotlin定义的ArrayList:
#ArrayList.kt
expect class ArrayList<E> : MutableList<E>, RandomAccess {
constructor()
constructor(initialCapacity: Int)
constructor(elements: Collection<E>)
//... 省略一些来自List、MutableCollection和MutableList的方法
//这些方法只有声明,没有具体实现。
}
复制代码
逛了一大圈,并没有找到一丝 Java 的 ArrayList 的痕迹.... Excuse me??? 说好的使用 Java 的 ArrayList ,但自己又创建了一个ArrayList.... 。最后将目标锁定在类声明的 expect 关键字,这是什么?最后在Kotlin官网中查到,这是Kotlin平台相关声明的 预期声明 !
在其他语言中,通常在公共代码中构建一组接口,并在平台相关模块中实现这些接口来实现多平台。然而,当在其中某个平台上已有一个实现所需功能的库,并且希望 直接使用 该库的API而无需额外包装器时,这种方法并不理想。
Kotlin 提供平台相关声明机制。 利用这种机制,公共模块中定义 预期声明 ,而平台模块提供与预期声明相对应的 实际声明 。
要点:
官网提供一个简单的例子:
#kt
//在公共模块中定义一个预期声明(不带任何实现)
expect class Foo(bar: String) {
fun frob()
}
fun main() {
Foo("Hello").frob()
}
复制代码
相应的 JVM 模块提供实现声明和相应的实现:
#kt
//提供实际声明
actual class Foo actual constructor(val bar: String) {
actual fun frob() {
println("Frobbing the $bar")
}
}
复制代码
如果有一个希望用在公共代码中的平台相关的库,同时为其他平台提供自己的实现。(像Java已提供好完整的集合库)那么可以将 现有类的别名 作为实际声明:
expect class AtomicRef<V>(value: V) {
fun get(): V
fun set(value: V)
fun getAndSet(value: V): V
fun compareAndSet(expect: V, update: V): Boolean
}
actual typealias AtomicRef<V> = java.util.concurrent.atomic.AtomicReference<V>
复制代码
而Java集合类作为实际声明的别名被定义在 TypeAliases.kt 中。这是我不知道 TypeAliases.kt 时的查找流程:
# TypeAliases.kt
@SinceKotlin("1.1") public actual typealias RandomAccess = java.util.RandomAccess
@SinceKotlin("1.1") public actual typealias ArrayList<E> = java.util.ArrayList<E>
@SinceKotlin("1.1") public actual typealias LinkedHashMap<K, V> = java.util.LinkedHashMap<K, V>
@SinceKotlin("1.1") public actual typealias HashMap<K, V> = java.util.HashMap<K, V>
@SinceKotlin("1.1") public actual typealias LinkedHashSet<E> = java.util.LinkedHashSet<E>
@SinceKotlin("1.1") public actual typealias HashSet<E> = java.util.HashSet<E>
复制代码
Kotlin定义一些集合类作为集合的通用层(使用 expect 定义预期声明),并将现有的Java集合类的别名作为实际声明,从而实现在JVM上直接使用Java的集合类。
可以从Kotlin官方文档中集合的变迁来观察(ArrayList为例):
从原本无ArrayList.kt,只有一系列对ArrayList.java的扩展属性与方法
-> 使用别名引用Java的ArrayList.java,ArrayList.kt服务于Js模块。
-> 使用平台相关声明,将ArrayList.kt作为 预期声明 ,并在JVM模块、Js模块、Native模块中提供具体的 实际声明 。使Kotlin对外提供"通用层"API,在不改变代码的情况下,实现跨平台。
但很奇怪....,创建只读集合的顶层函数(listOf、setOf和mapOf)源码中,直接使用java的 Collections$SingletonList、Collections$SingletonSet 和 Collections$SingletonMap。望大佬告知其中原由 0.0。
//listOf public fun <T> listOf(element: T): List<T> = java.util.Collections.singletonList(element) //setOf public fun <T> setOf(element: T): Set<T> = java.util.Collections.singleton(element) //mapOf public fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> = java.util.Collections.singletonMap(pair.first, pair.second) 复制代码
了解了一波Kotlin的集合后,需要回归到对集合的使用上——集合的函数式API。
基本定义:
filter函数遍历集合并返回给定lambda中返回true的元素。
源码:
#_Collection.kt
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
//创建一个新的集合并连同lambda一起传递给filterTo()
return filterTo(ArrayList<T>(), predicate)
}
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
//遍历原集合
for (element in this)
//执行lambda,如返回为true,则将该元素添加到新集合中
if (predicate(element))
destination.add(element)
//返回新集合
return destination
}
复制代码
解析:
创建一个新的ArrayList对象,遍历原集合,将lambda表达式返回true的元素添加到新ArrayList对象中,最后返回新的ArrayList对象。
基本定义:
map函数对集合中每一个元素应用给定的函数,并把结果收集到一个新集合。
源码:
#_Collection.kt
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
//创建一个新的集合并连同lambda一起传递给mapTo()
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.mapTo(destination: C, transform: (T) -> R): C {
//遍历旧集合元素
for (item in this)
//执行lambda,对元素进行处理,将返回值添加到新集合中
destination.add(transform(item))
//返回新集合
return destination
}
复制代码
解析:
创建一个新的ArrayList集合,遍历原集合,将函数类型对象处理过的值添加到新ArrayList对象中,并返回新的ArrayList对象。
基本定义:
对集合元素进行分组,并返回一个Map集合,存储元素分组依据的键和元素分组
源码:
#_Collection.kt
public inline fun <T, K> Iterable<T>.groupBy(keySelector: (T) -> K): Map<K, List<T>> {
//创建一个新的map并连同lambda一起传递给groupByTo()
return groupByTo(LinkedHashMap<K, MutableList<T>>(), keySelector)
}
public inline fun <T, K, M : MutableMap<in K, MutableList<T>>> Iterable<T>.groupByTo(destination: M, keySelector: (T) -> K): M {
//遍历旧集合元素
for (element in this) {
//执行lambda,对元素进行处理,将返回值作为key
val key = keySelector(element)
//使用得到的key在新的map中获取vlaue,如果没有则创建一个ArrayList对象,作为value存储到map中,并返回ArrayList对象。
val list = destination.getOrPut(key) { ArrayList<T>() }
//对ArrayList对象添加当前元素
list.add(element)
}
//返回新集合
return destination
}
复制代码
解析:
创建一个LinkedHashMap对象,遍历旧集合的元素,将函数类型对象处理过的值作为key,对应的元素存储到一个ArrayList中,并将该ArrayList对象作为map的value进行存储。返回LinkedHashMap对象。
基本定义:
根据实参给定的函数对集合中的每个元素做交换(映射),然后把多个列表平铺成一个列表。
源码:
#_Collection.kt
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
//创建一个新的集合并连同lambda一起传递给flatMapTo()
return flatMapTo(ArrayList<R>(), transform)
}
public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
////遍历旧集合元素
for (element in this) {
//执行lambda,对元素进行处理,返回一个集合
val list = transform(element)
//在得到的集合添加到新的集合中。
destination.addAll(list)
}
//返回新集合
return destination
}
复制代码
解析:
创建一个新的ArrayList集合,遍历原集合,对原集合的元素转换成列表,最后将转换得到的列表存储到新的ArrayList集合中,并返回新的ArrayList对象。
基本定义:
检查集合中的所有元素是否都符合或是否存在符合的元素。
源码:
#_Collection.kt
//any
public inline fun <T> Iterable<T>.any(predicate: (T) -> Boolean): Boolean {
//判断他是否为空,如果集合为空集合,直接返回false,因为肯定不存在
if (this is Collection && isEmpty())
return false
for (element in this)
//遍历元素的过程中,如果有其中一个元素满足条件,则直接返回true
if (predicate(element))
return true
//最后都不行,就返回false
return false
}
//all
public inline fun <T> Iterable<T>.all(predicate: (T) -> Boolean): Boolean {
//如果集合为空集合,直接返回true
if (this is Collection && isEmpty())
return true
for (element in this)
//遍历元素的过程中,只要有其中一个元素满足条件,则直接返回false
if (!predicate(element))
return false
return true
}
复制代码
基本定义:
检查有多少满足条件的元素数量。
源码:
#_Collection.kt
public inline fun <T> Iterable<T>.count(predicate: (T) -> Boolean): Int {
if (this is Collection && isEmpty())
return 0
//弄一个临时变量记录数量
var count = 0
//遍历元素
for (element in this)
//如果满足添加,则数量+1
if (predicate(element))
checkCountOverflow(++count)
return count
}
复制代码
基本定义:
寻找第一个符合条件的元素,如果没有符合条件的元素,则返回null。
源码:
#_Collection.kt
public inline fun <T> Iterable<T>.find(predicate: (T) -> Boolean): T? {
//将lambda传给firstOrNull()
return firstOrNull(predicate)
}
public inline fun <T> Iterable<T>.firstOrNull(predicate: (T) -> Boolean): T? {
for (element in this)
//遍历的元素中,返回第一个符合满足添加的元素。
if (predicate(element))
return element
//没找到,则返回null
return null
}
复制代码
Kotlin数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数。
在Kotlin中提供以下方法创建数组:
val array = Array<String>(5){
it.toChar() + "a"
}
复制代码
Kotlin最常见的创建数组的情况是:调用需要数组为参数的Java方法,或调用带有vararg参数的Kotlin函数。这时需要使用toTypeArray()将集合转换成数组。
val list = listOf("daqi","java","kotlin")
//集合转数组
list.toTypedArray()
val array = arrayOf("")
//数组转集合
array.toList()
复制代码
Array类的类型参数决定了创建的是一个基本数据类型装箱的数组。当需要创建没有装箱的基本数据类型的数组时,必须使用基本数据类型数组。Kotlin为每一种基本数据类型提供独立的基本数据类型数组。例如:Int类型的数组叫做IntArray。基本数据类型数组会被编译成普通的Java基本数据类型的数组,如int[].因此基本数据类型数组在存储值时并没有装箱。
创建基本数据类型数组:
Kotlin标准库中对集合的支持扩展库(filter、map等)一样适用于数组,包括基本数据类型的数组。