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知識概括(十四) —— 反射

相關文章
相關標籤/搜索