Kotlin 中的集合

今天一起来探索下 Kotlin 中的集合,集合可以说是编程语言中最为常用以及核心语法点之一。集合一般用于结构化数据存储,我们都很熟悉 Java 中的集合。其实 Kotlin 中的集合与 Java 中的集合还是有一些不同的。最大不同点在于在 Kotlin 中集合是严格分类,分为只读和可变类似于 Kotlin 中的变量的只读和可变,然后针对不同的场景来选择使用只读集合或可变集合。所以下面会展开详细介绍 Kotlin 中的集合。

1. 集合的分类

在 Kotlin 中集合主要分为可变集合只读集合,其中可变集合使用 "Mutable" 前缀 + 集合类名表示,比如 MutableList、MutableSet、MutableMap 等。而对于只读集合就是和 Java 中集合类名是一致。其实说到这里,就会发现一个有趣问题 Java 中的 List 而非 Kotlin 中的 List , 因为 Kotlin 中的 List 是只读的而非可变,却有点类似于 Kotlin 中的 MutableList

图片描述

1.1 只读集合

在集合内部只具有访问元素的方法,不具有类似 add、remove、clear 之类修改集合的方法。比如 Collection<E> 只读集合内部就没有 add、remove、clear 之类方法,只有 get 访问元素的方法。
图片描述

具体可以参考 Kotlin 中 Collection 的源码:

public interface Collection<out E> : Iterable<E> {
    // Query Operations
    public val size: Int
    public fun isEmpty(): Boolean
    public operator fun contains(element: @UnsafeVariance E): Boolean
    override fun iterator(): Iterator<E>
    public fun containsAll(elements: Collection<@UnsafeVariance E>): Boolean
}

1.2 可变集合

在集合内部既具有访问元素的方法,也具有类似 add、remove、clear 之类修改集合的方法。比如 MutableCollection<E> 可变集合内部就有 add、remove、clear 之类方法。

图片描述

具体可以参考 Kotlin 中 MutableCollection 的源码:

public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {
    // Query Operations
    override fun iterator(): MutableIterator<E>

    // Modification Operations
    public fun add(element: E): Boolean

    public fun remove(element: E): Boolean

    // Bulk Modification Operations
    public fun addAll(elements: Collection<E>): Boolean

    public fun removeAll(elements: Collection<E>): Boolean

    public fun retainAll(elements: Collection<E>): Boolean

    public fun clear(): Unit
}

下面是只读集合 Collection<T> 与可变集合 MutableCollection<T> 的区别。
图片描述

2. 集合类型系统划分

通过 Collection.kt 文件中可以了解到有这些集合 Iterable (只读迭代器) 和 MutableIterable (可变迭代器)、 CollectionMutableCollectionListMutableListSetMutableSetMapMutableMap 。那么它们之间的类关系图是怎样的。

IterableMutableIterable 接口分别是只读和可变集合的父接口, Collection 继承 Iterable 然后 List、Set 接口继承自 CollectionMap 接口比较特殊它是单独的接口,然后 MutableMap 接口是继承自 Map

image.png

3. 常见集合的使用

3.1 List (只读集合) 和 MutableList (可变集合)

集合初始化

Tips: 在 Kotlin 中 List 只读集合初始化主要是通过 listOf<T> 函数,而 MutableList 可变集合初始化主要是通过 mutableListOf<T> 函数。

List 初始化:

fun listInit() {
    //通过listOf<T>(xx,xx,xx)方法初始化,带初始化元素的集合
    val languages1: List<String> = listOf<String>("kotlin", "dart", "java", "swift")

    //List<String>可以不用声明,通过listOf<String>可以类型推导出具体类型
    val languages2 = listOf<String>("kotlin", "dart", "java", "swift")

    //<String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是String
    val languages3 = listOf("kotlin", "dart", "java", "swift")

    //通过listOf<T>函数初始化空集合
    val emptyLanguages1: List<String> = listOf<String>()

    //<String>泛型类型可以不用声明,通过List<String>可以知道是泛型类型是String
    val emptyLanguages2: List<String> = listOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = listOf()

    //空集合还可以通过emptyList<T>函数来实现,实际上通过源码发现listOf<T>(),最后都是委托调用emptyList<T>()
    val emptyLanguages4 = emptyList<String>()
}

MutableList 初始化:

fun mutableListInit() {
    //通过mutableListOf<T>(xx,xx,xx)函数初始化,带初始化元素的集合
    val languages1: MutableList<String> = mutableListOf<String>("kotlin", "dart", "java", "swift")

    //MutableList<String>可以不用声明,通过mutableListOf<String>可以类型推导出具体类型
    val languages2 = mutableListOf<String>("kotlin", "dart", "java", "swift")

    //<String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是String
    val languages3 = mutableListOf("kotlin", "dart", "java", "swift")

    //mutableListOf<T>函数初始化可变空集合
    val emptyLanguages1: MutableList<String> = mutableListOf<String>()

    //<String>泛型类型可以不用声明,通过MutableList<String>可以知道是泛型类型是String
    val emptyLanguages2: MutableList<String> = mutableListOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = mutableListOf()

    //可变空集合还可以通过ArrayList<T>函数来实现,实际上通过源码发现mutableListOf<T>(),最后都是委托调用ArrayList<T>()
    //而且ArrayList<T>实际上就是Java中的ArrayList<T>,只不过是Kotlin中取了一个别名而已,关于具体内容请参考这个类kotlin.collections.TypeAliasesKt实现
    val emptyLanguages4: MutableList<String> = ArrayList<String>()
}

集合遍历

Tips: List 和 MutableList 的遍历是一样的.

List 遍历:

fun main() {
    val languageList = listOf("kotlin", "dart", "java", "swift")

    //for-in
    for (language in languageList) {
        println(language)
    }

    //for-each
    languageList.forEach { println(it) }

    //for-in-indices
    for (index in languageList.indices) {
        println(languageList[index])
    }

    //while-iterator
    val iterator = languageList.iterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}

MutableList 遍历:

fun main() {
    val languageList = mutableListOf("kotlin", "dart", "java", "swift")
    languageList.add("ruby")
    languageList.add("python")

    //for-in
    for (language in languageList) {
        println(language)
    }

    //for-each
    languageList.forEach { println(it) }

    //for-in-indices
    for (index in languageList.indices) {
        println(languageList[index])
    }

    //while-iterator
    val iterator = languageList.iterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}

3.2 Set (只读集合) 和 MutableSet (可变集合)

集合初始化

在 Kotlin 中 Set 只读集合初始化主要是通过 setOf<T> 函数,而 MutableSet 可变集合初始化主要是通过 mutableSetOf<T> 函数。

Set 初始化:

fun setInit() {
    //通过setOf<T>(xx,xx,xx)函数初始化,带初始化元素的集合
    val languages1: Set<String> = setOf<String>("kotlin", "dart", "java", "swift")

    //Set<String>可以不用声明,通过setOf<String>可以类型推导出具体类型
    val languages2 = setOf<String>("kotlin", "dart", "java", "swift")

    //<String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是String
    val languages3 = setOf("kotlin", "dart", "java", "swift")

    //通过setOf<T>函数初始化空集合
    val emptyLanguages1: Set<String> = setOf<String>()

    //<String>泛型类型可以不用声明,通过List<String>可以知道是泛型类型是String
    val emptyLanguages2: Set<String> = setOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = setOf()

    //空集合还可以通过emptySet<T>函数来实现,实际上通过源码发现setOf<T>(),最后都是委托调用emptySet<T>()
    val emptyLanguages4 = emptySet<String>()
}

MutableSet 初始化:

fun mutableSetInit() {
    //通过mutableSetOf<T>(xx,xx,xx)函数初始化,带初始化元素的集合
    val languages1: MutableSet<String> = mutableSetOf<String>("kotlin", "dart", "java", "swift")

    //MutableSet<String>可以不用声明,通过mutableSetOf<String>可以类型推导出具体类型
    val languages2 = mutableSetOf<String>("kotlin", "dart", "java", "swift")

    //<String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是String
    val languages3 = mutableSetOf("kotlin", "dart", "java", "swift")

    //mutableSetOf<T>函数初始化空集合
    val emptyLanguages1: MutableSet<String> = mutableSetOf<String>()

    //<String>泛型类型可以不用声明,通过MutableSet<String>可以知道是泛型类型是String
    val emptyLanguages2: MutableSet<String> = mutableSetOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = mutableSetOf()

    //空集合还可以通过LinkedHashSet<T>函数来实现,实际上通过源码发现mutableSetOf<T>(),最后都是委托调用LinkedHashSet<T>()
    //而且LinkedHashSet<T>实际上就是Java中的LinkedHashSet<T>,只不过是Kotlin中取了一个别名而已
    val emptyLanguages4: MutableSet<String> = LinkedHashSet<String>()
}

集合遍历

Tips:Set 和 MutableSet 的遍历是一样的。

Set 的遍历:

fun main() {
    val languageSet = setOf("kotlin", "dart", "java", "swift")

    //for-in
    for (language in languageSet) {
        println(language)
    }

    //for-each
    languageSet.forEach { println(it) }

    //for-in-indices
    for (index in languageSet.indices) {
        println(languageSet.elementAt(index))//注意,Set是没有get方法运算符重载函数的,
        // 所以不能直接通过languageSet[index]获取,但是可以通过扩展函数elementAt获取
    }

    //while-iterator
    val iterator = languageSet.iterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}
  • MutableSet 的遍历
fun main() {
    val languageSet = mutableSetOf("kotlin", "dart", "java", "swift")
    languageSet.add("ruby")
    languageSet.add("python")

    //for-in
    for (language in languageSet) {
        println(language)
    }

    //for-each
    languageSet.forEach { println(it) }

    //for-in-indices
    for (index in languageSet.indices) {
        println(languageSet.elementAt(index))//注意,Set是没有get方法运算符重载函数的,
        // 所以不能直接通过languageSet[index]获取,但是可以通过扩展函数elementAt获取
    }

    //while-iterator
    val iterator = languageSet.iterator()
    while (iterator.hasNext()) {
        println(iterator.next())
    }
}

3.3 Map (只读集合) 和 MutableMap (可变集合)

集合初始化

Map 的初始化:

fun mapInit() {
    //通过mapOf<K,V>(xx to xx,xx to xx,xx to xx)函数初始化,带初始化元素的集合,
    val languages1: Map<String, String> = mapOf<String, String>(
        "1" to "kotlin",
        "2" to "dart",
        "3" to "java",
        "4" to "swift"
    )
    // xx to xx实际上是Pair<K,V>的中缀表达式简写形式
    val languages2: Map<String, String> = mapOf<String, String>(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )

    //Map<K,V>可以不用声明,通过mapOf<String,String>可以类型推导出具体类型
    val languages3 = mapOf<String, String>(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )

    //<String, String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是<String, String>
    val languages4 = mapOf(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )
    //通过mapOf<K,V>函数初始化空集合
    val emptyLanguages1: Map<String, String> = mapOf<String, String>()

    //<String, String>泛型类型可以不用声明,通过Map<String, String>可以知道是泛型类型是<String, String>
    val emptyLanguages2: Map<String, String> = mapOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = mapOf()

    //空集合还可以通过emptyMap<K,V>函数来实现,实际上通过源码发现mapOf<K,V>(),最后都是委托调用emptyMap<K,V>()
    val emptyLanguages4 = emptyMap<String, String>()
}

MutableMap 的初始化:

fun mutableMapInit() {
    //通过mutableMapOf<K,V>(xx to xx,xx to xx,xx to xx)函数初始化,带初始化元素的集合,
    val languages1: MutableMap<String, String> = mutableMapOf<String, String>(
        "1" to "kotlin",
        "2" to "dart",
        "3" to "java",
        "4" to "swift"
    )
    // xx to xx实际上是Pair<K,V>的中缀表达式简写形式
    val languages2: MutableMap<String, String> = mutableMapOf<String, String>(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )

    //MutableMap<K,V>可以不用声明,通过mutableMapOf<String,String>可以类型推导出具体类型
    val languages3 = mutableMapOf<String, String>(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )

    //<String, String>泛型类型可以不用声明,通过集合内部元素初始化值的类型,可以推导出元素类型是<String, String>
    val languages4 = mutableMapOf(
        Pair("1","kotlin"),
        Pair("2","dart"),
        Pair("3","java"),
        Pair("4","swift")
    )
    //通过mapOf<K,V>函数初始化空集合
    val emptyLanguages1: MutableMap<String, String> = mutableMapOf<String, String>()

    //<String, String>泛型类型可以不用声明,通过MutableMap<String, String>可以知道是泛型类型是<String, String>
    val emptyLanguages2: MutableMap<String, String> = mutableMapOf()

    //注意: 这是不合法的,因为无法推导出具体元素类型
//    val emptyLanguages3 = mutableMapOf()

    //空集合还可以通过LinkedHashMap<K,V>函数来实现,实际上通过源码发现mutableMapOf<K,V>(),最后都是委托调用LinkedHashMap<K,V>()
    //而且LinkedHashMap<K,V>实际上就是Java中的LinkedHashMap<K,V>,只不过是Kotlin中取了一个别名而已
    val emptyLanguages4: MutableMap<String, String> = HashMap<String, String>()
}

集合的遍历

Tips:Map 和 MutableMap 的遍历是一样的.

Map 的遍历:

fun main() {
    val languageMap: Map<String, String> = mapOf<String, String>("1" to "kotlin", "2" to "dart", "3" to "java")

    //for-in-entry
    for (entry in languageMap) {
        println("key: ${entry.key} ---> value: ${entry.value}")
    }

    //for-in-解构声明
    for ((key, value) in languageMap) {
        println("key: $key ---> value: $value")
    }

    //for-each
    languageMap.forEach { key, value -> println("key: $key ---> value: $value") }

    //for-each-解构声明
    languageMap.forEach { (key, value) -> println("key: $key ---> value: $value") }

    //for-in-Map.Entry
    for (entry in languageMap.entries) {
        println("key: ${entry.key} ---> value: ${entry.value}")
    }

    //for-each-Map.Entry
    languageMap.entries.forEach { entry -> println("key: ${entry.key} ---> value: ${entry.value}") }

    //for-each-index-Map.Entry
    languageMap.entries.forEachIndexed { index, entry -> println("index: $index -----> key: ${entry.key} ---> value: ${entry.value}") }

    //for-in-Map中的key
    for (key in languageMap.keys) {
        println("key: $key")
    }

    //for-each-Map中的key
    languageMap.keys.forEach { println(it) }

    //for-in-Map中的value
    for (value in languageMap.values) {
        println("value: $value")
    }

    //for-each-Map中的value
    languageMap.values.forEach { println(it) }

    //while-iterator
    val iterator = languageMap.iterator()
    while (iterator.hasNext()) {
        val entry = iterator.next()
        println("key: ${entry.key} ---> value: ${entry.value}")
    }
}

MutableMap 的遍历:

fun main() {
    val languageMap: MutableMap<String, String> = mutableMapOf<String, String>("1" to "kotlin", "2" to "dart", "3" to "java")
    languageMap["4"] = "swift"
    languageMap.putAll(mapOf("5" to "ruby", "6" to "python"))

    //for-in-entry
    for (entry in languageMap) {
        println("key: ${entry.key} ---> value: ${entry.value}")
    }

    //for-in-解构声明
    for ((key, value) in languageMap) {
        println("key: $key ---> value: $value")
    }

    //for-each
    languageMap.forEach { key, value -> println("key: $key ---> value: $value") }

    //for-each-解构声明
    languageMap.forEach { (key, value) -> println("key: $key ---> value: $value") }

    //for-in-Map.Entry
    for (entry in languageMap.entries) {
        println("key: ${entry.key} ---> value: ${entry.value}")
    }

    //for-each-Map.Entry
    languageMap.entries.forEach { entry -> println("key: ${entry.key} ---> value: ${entry.value}") }

    //for-each-index-Map.Entry
    languageMap.entries.forEachIndexed { index, entry -> println("index: $index -----> key: ${entry.key} ---> value: ${entry.value}") }

    //for-in-Map中的key
    for (key in languageMap.keys) {
        println("key: $key")
    }

    //for-each-Map中的key
    languageMap.keys.forEach { println(it) }

    //for-in-Map中的value
    for (value in languageMap.values) {
        println("value: $value")
    }

    //for-each-Map中的value
    languageMap.values.forEach { println(it) }

    //while-iterator
    val iterator = languageMap.iterator()
    while (iterator.hasNext()) {
        val entry = iterator.next()
        println("key: ${entry.key} ---> value: ${entry.value}")
    }
}

4. 常见集合使用函数式 API

map 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    numbers
        .map { "This Number is: $it" }//Int元素类型转化String类型元素
        .forEach { print(it) }
}

filter 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    numbers
        .filter { it <=3 }//筛选元素,只有小于等于3元素才会被输出
        .forEach { print(it) }
}

forEach 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    numbers.forEach { print(it) }//遍历输出
}

drop 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    numbers.drop(3).forEach { println(it) }//根据传入数值3,表示从左到右顺序地删除3个集合中的元素,并返回集合中剩余的元素。
}

take 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    numbers.take(3).forEach { println(it) }//根据传入数值3,表示从左到右顺序地取前3个集合中的元素,剩下元素直接丢弃。
}

find 函数:

fun main() {
    val languages: List<Int> = listOf(1, 2, 3, 4)
    val element: Int? = languages.find { it == 3 }//查找集合符合条件首次出现的元素,找不到返回null
    println(element ?: "Not Found")
}

joinToString 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    println(numbers.joinToString(separator = "-", prefix = "<", postfix = ">"))//将集合中元素用separator分割符连接起来,并且在前后加上前缀和后缀
    //输出结果: <1-2-3-4>
}

first 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    println(numbers.first())//取出集合中第一个元素
}

single 函数:

fun main() {
    val numbers: List<Int> = listOf(1)
    println(numbers.single())//single函数取出集合唯一的一个元素,并且该集合只有一个元素,否则就会抛出异常
}

last 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    println(numbers.last())//取出集合中最后一个元素
}

sum 函数:

fun main() {
    val numbers: List<Int> = listOf(1, 2, 3, 4)
    println(numbers.sum())//累加求和函数,不过只针对数字类型元素集合,Number类型就包括了Int、Float、Double
}

sort、sortedDescending 函数:

fun main() {
    val numbers: List<Int> = listOf(2, 1, 4, 3)
    println(numbers.sorted())//升序排序
    println(numbers.sortedDescending())//降序排序
}

5. 使用 Kotlin 中集合几点建议

  • 在代码的任何地方都优先使用只读集合,只在需要修改集合的情况下才去使用可变集合;
  • 只读集合不一定是不可变的,关于这个只读和不可变类似于 val 的只读和不可变原理;
  • 不能把一个只读类型的集合作为参数传递给一个带可变类型集合的函数。

6. 集合平台类型转化规则

根据可空性平台类型原理,Kotlin 中是无法知道可空性信息的类型,既可以把它当做可空类型又可以把它当做非空类型。集合的平台类型和这个类似,在 Java 中声明的集合类型的变量也被看做平台类型一个平台类型的集合本质上就是可变性未知的集合,Kotlin 中可以把它看做是只读的集合或者是可变的集合. 实际上这都不是很重要,因为你只需要根据你的需求选择即可,想要执行的所有操作都能正常工作,它不像可空性平台存在额外判断操作以及空指针风险。

注意:可是当你决定使用哪一种 Kotlin 类型表示 Java 中集合类型的变量时,需要考虑以下 3 种情况:

1. 集合是否为空?

如果为空转换成 Kotlin 中集合后面添加 ?,例如 Java 中的 List<String> 转化成 Kotlin 中的 List<String>?

2. 集合中的元素是否为空?

如果为空转换成 Kotlin 中集合泛型实参后面添加 ?,例如 Java 中的 List<String> 转化成 Kotlin 中的 List<String?>

3. 操作方法会不会修改集合?(集合的只读或可变)

如果是只读的,例如 Java 中的 List<String> 转化成 Kotlin 中的 List<String>;如果是可变的,例如 Java 中的 List<String> 转化成 Kotlin 中的 MutableList<String>.

注意: 当然上面三种情况可以一种或多种同时出现,那么转化成 Kotlin 中的集合类型也是多种情况最终重组的类型。

7. 总结

到这里有关 Kotlin 的集合就阐述到这里,简单回顾下,这节课从整体上介绍了 Kotlin 中集合的分类,分为可变和只读,关于这一点很重要,一定要树立这种分类的意识。因为只有分类了并总结它们的不同特点,才能不同场景中灵活运用它。然后就是介绍了常见集合的使用其中包括初始化、遍历以及一些常用 API 函数的使用,最后给出使用集合几点建议和经验。