Kotlin知识概括(七) —— 集合

前序

      Kotlin没有本身的集合库,彻底依赖Java标准库中的集合类,并经过扩展函数增长特性来加强集合。意味着Kotlin与Java交互时,永远不须要包装或者转换这些集合对象,大大加强与Java的互操做性。html

只读集合和可变集合

      Kotlin与Java最大的不一样之一就是:Kotlin将集合分为只读集合和可变集合。这种区别源自最基础的集合接口:kotlin.collections.Collection。该接口能够对集合进行一些基本操做,但无任何添加和移除元素的方法。java

      只有实现 kotlin.collections.MutableCollection 接口才能够修改集合的数据。MutableCollection 接口继承自 Collection,并提供添加、移除和清空集合元素的方法。当一个函数接收 Collection,而不是 MutableCollection,即意味着函数不对集合作修改操做。android

      可变集合通常都带有 “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 的生成的只读集合(listOfsetOfmapOf)与可变集合(mutableListmutableSetOfmutableMapOf)生成的是什么Java类型集合,作了一个小实验(分别对应空集合、单元素集合和多元素集合):安全

  • 一、使用在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());
}
复制代码
  • 二、在Kotlin中建立只读集合和可变集合,并将其传入以前声明的Java静态方法中进行打印:
#daqiKotlin.kt
fun main(args: Array<String>) {
    val emptyList = listOf<Int>()
    val emptySet = setOf<Int>()
    val emptyMap = mapOf<Int,Int>()
    
    val initList = listOf(1)
    val initSet = setOf(2)
    val initMap = mapOf(1 to 1)
    
    val list = listOf(1,2)
    val set = setOf(1,2)
    val map = mapOf(1 to 1,2 to 2)

    println("空元素只读集合")
    collectionsType(emptyList)
    collectionsType(emptySet)
    mapType(emptyMap)
    
    println("单元素只读集合")
    collectionsType(initList)
    collectionsType(initSet)
    mapType(initMap)
    
    println("多元素只读集合")
    collectionsType(list)
    collectionsType(set)
    mapType(map)

    println("-----------------------------------------------------------------")

    val emptyMutableList = mutableListOf<Int>()
    val emptyMutableSet = mutableSetOf<Int>()
    val emptyMutableMap = mutableMapOf<Int,Int>()

    val initMutableList = mutableListOf(1)
    val initMutableSet = mutableSetOf(2)
    val initMutableMap = mutableMapOf(1 to 1)


    val mutableList = mutableListOf(1,2)
    val mutableSet = mutableSetOf(1,2)
    val mutableMap = mutableMapOf(1 to 1,2 to 2)

    println("空元素可变集合")
    collectionsType(emptyMutableList)
    collectionsType(emptyMutableSet)
    mapType(emptyMutableMap)

    println("单元素可变集合")
    collectionsType(initMutableList)
    collectionsType(initMutableSet)
    mapType(initMutableMap)

    println("多元素可变集合")
    collectionsType(mutableList)
    collectionsType(mutableSet)
    mapType(mutableMap)
}
复制代码

结果:bash

      能够得出只读集合( listOfsetOfmapOf)与可变集合( mutableListmutableSetOfmutableMapOf)对应Java集合的关系表:

方法 Java类型
listOf() kotlin.collections.EmptyList
setOf() kotlin.collections.EmptySet
mapOf() kotlin.collections.EmptyMap
listOf(element: T) java.util.Collections$SingletonList
setOf(element: T) java.util.Collections$SingletonSet
mapOf(pair: Pair<K, V>) java.util.Collections$SingletonMap
listOf(vararg elements: T) java.util.Arrays$ArrayList
setOf(vararg elements: T) java.util.LinkedHashSet
mapOf(vararg pairs: Pair<K, V>) java.util.LinkedHashMap
mutableList() java.util.ArrayList
mutableSetOf() java.util.LinkedHashSet
mutableMapOf() java.util.LinkedHashMap

型变

      只读集合类型是型变的。当类 Rectangle 继承自 Shape,则能够在须要 List<Shape> 的任何地方使用 List<Rectangle>。 由于集合类型与元素类型具备相同的子类型关系。 Map在值类型上是型变的,但在键类型上不是。数据结构

      可变集合不是型变的。 MutableList <Rectangle>MutableList <Shape> 的子类型,当你插入其余 Shape 的继承者(例如,Circle),从而违反了它的 Rectangle 类型参数。多线程

集合的可空性

      对于任何类型,均可以对其声明为可空类型,集合也不例外。你能够将集合元素的类型设置为可空,也能够将集合自己设置为可空,须要清楚是集合的元素可空仍是集合自己可空。并发

Kotlin集合的秘密:平台相关声明

寻找java.util.ArrayList

      学习 Kotlin 的时候,经常被告知 Kotlin 直接使用的是原生 Java 集合,抱着探究真相的心态,点进了建立集合的顶层方法 mutableListOf()dom

#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 提供平台相关声明机制。 利用这种机制,公共模块中定义预期声明,而平台模块提供与预期声明相对应的实际声明

要点:

  • 公共模块中的预期声明与其对应的实际声明始终具备彻底相同的完整限定名。
  • 预期声明标有 expect 关键字;实际声明标有 actual 关键字。
  • 与预期声明的任何部分匹配的全部实际声明都须要标记为 actual。
  • 预期声明 决不包含任何实现代码。

官网提供一个简单的例子:

#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的集合类。

ArrayList的变迁

能够从Kotlin官方文档中集合的变迁来观察(ArrayList为例):

  • 1.0版本ArrayList:
  • 1.1版本ArrayList:
  • 1.3版本ArrayList:

      从本来无ArrayList.kt,只有一系列对ArrayList.java的扩展属性与方法

-> 使用别名引用Java的ArrayList.java,ArrayList.kt服务于Js模块。

-> 使用平台相关声明,将ArrayList.kt做为预期声明,并在JVM模块、Js模块、Native模块中提供具体的实际声明。使Kotlin对外提供"通用层"API,在不改变代码的状况下,实现跨平台。

只读集合与平台相关声明

      当对应单个或多个初始化值的集合时,其使用的都是Java的集合类型,一块儿探究下是否也与平台相关声明有关:

单元素只读集合

      建立单元素集合的listOf(element: T)setOf(element: T)mapOf(pair: Pair<K, V>)直接做为顶层函数声明在JVM模块中,并直接使用Java的单元素集合类进行初始化。

#CollectionsJVM.kt
//listOf
public fun <T> listOf(element: T): List<T> =
java.util.Collections.singletonList(element)
复制代码
#SetsJVM.kt
//setOf
public fun <T> setOf(element: T): Set<T> =
java.util.Collections.singleton(element)
复制代码
#MapsJVM.kt
//mapOf
public fun <K, V> mapOf(pair: Pair<K, V>): Map<K, V> =
java.util.Collections.singletonMap(pair.first, pair.second)
复制代码

多元素只读集合

      建立多元素集合的顶层函数的参数都带有vararg声明,这相似于Java的可变参数,接收任意个数的参数值,并打包为数组。

  • listOf(vararg elements: T):
#Collections.kt
public fun <T> listOf(vararg elements: T): List<T> = 
if (elements.size > 0) elements.asList() else emptyList()
复制代码

listOf(vararg elements: T)函数会直接将可变参数转换为list:

#_Arrays.kt
public expect fun <T> Array<out T>.asList(): List<T>
复制代码

Array.asList()拥有 expect 关键字,即做为预期声明存在,这意味着JVM模块会提供对应的实现:

#_ArraysJvm.kt
public actual fun <T> Array<out T>.asList(): List<T> {
    return ArraysUtilJVM.asList(this)
}
复制代码
#ArraysUtilJVM.java
class ArraysUtilJVM {
    static <T> List<T> asList(T[] array) {
        return Arrays.asList(array);
    }
}
复制代码

在JVM模块中提供了实际声明的Array.asList(),并调用了java.util.Arrays.asList(),返回java.util.Arrays的静态内部类java.util.Arrays$ArrayList对象。

  • setOf(vararg elements: T):
#Sets.kt
public fun <T> setOf(vararg elements: T): Set<T> = 
if (elements.size > 0) elements.toSet() else emptySet()
复制代码

setOf(vararg elements: T)函数会直接将可变参数转换为set:

public fun <T> Array<out T>.toSet(): Set<T> {
    return when (size) {
        0 -> emptySet()
        1 -> setOf(this[0])
        else -> toCollection(LinkedHashSet<T>(mapCapacity(size)))
    }
}
复制代码

并和mutableSetOf()同样,使用Kotlin的LinkedHashSet依托平台相关声明建立java.util.LinkedHashSet对象。(具体转换逻辑不深究)

  • mapOf(vararg pairs: Pair<K, V>)
public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> =
    if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()
复制代码

并和mutableMapOf()同样,使用Kotlin的LinkedHashMap依托平台相关声明建立java.util.LinkedHashMap对象。(具体转换逻辑不深究)

集合的函数式API

      了解了一波Kotlin的集合后,须要回归到对集合的使用上——集合的函数式API。

filter函数

基本定义:

      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函数

基本定义:

      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对象。

groupBy函数

基本定义:

      对集合元素进行分组,并返回一个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对象做为mapvalue进行存储。返回LinkedHashMap对象。

flatMap函数

基本定义:

      根据实参给定的函数对集合中的每一个元素作交换(映射),而后把多个列表平铺成一个列表。

源码:

#_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对象。

all函数 和 any函数

基本定义:

      检查集合中的全部元素是否都符合或是否存在符合的元素。

源码:

#_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
}
复制代码

count函数

基本定义:

      检查有多少知足条件的元素数量。

源码:

#_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
}
复制代码

find函数

基本定义:

      寻找第一个符合条件的元素,若是没有符合条件的元素,则返回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
}
复制代码

集合使用的注意事项

  • 优先使用只读集合,只有在须要修改集合的状况下才使用可变集合。
  • 只读集合不必定是不可变的。若是你使用的变量是只读接口的类型,该变量可能引用的是一个可变集合。由于只读接口Collection是全部集合的"基类"
  • 只读集合并不老是线程安全的。若是须要在多线程环境中处理数据,必须使用支持并发访问的数据结构。

数组

      Kotlin数组是一个带有类型参数的类,其元素类型被指定为相应的类型参数。

在Kotlin中提供如下方法建立数组:

  • arrayOf函数,该函数的实参做为数组的元素。
  • arrayOfNulls函数,建立一个给定大小的数组,包含的是null值。通常用来建立元素类型可空的数组
  • Array构造方法,接收一个数组的大小和lambda表达式。lambda表达式用来建立每个数组元素,不能显式地传递每个元素。
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[].所以基本数据类型数组在存储值时并无装箱。

建立基本数据类型数组:

  • 工厂方法(例如intArrayOf)接收变长参数并建立存储这些值的数组。
  • 基本数据类型数组的构造方法。

      Kotlin标准库中对集合的支持扩展库(filtermap等)同样适用于数组,包括基本数据类型的数组。

参考资料:

android Kotlin系列:

Kotlin知识概括(一) —— 基础语法

Kotlin知识概括(二) —— 让函数更好调用

Kotlin知识概括(三) —— 顶层成员与扩展

Kotlin知识概括(四) —— 接口和类

Kotlin知识概括(五) —— Lambda

Kotlin知识概括(六) —— 类型系统

Kotlin知识概括(七) —— 集合

Kotlin知识概括(八) —— 序列

Kotlin知识概括(九) —— 约定

Kotlin知识概括(十) —— 委托

Kotlin知识概括(十一) —— 高阶函数

Kotlin知识概括(十二) —— 泛型

Kotlin知识概括(十三) —— 注解

Kotlin知识概括(十四) —— 反射

相关文章
相关标签/搜索