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等)一样适用于数组,包括基本数据类型的数组。