Kotlin语法基础篇十四:集合

前言

在上一篇文章中我们详细的介绍了Kotlinby关键字的使用。这篇文章我们将介绍Kotlin中集合的使用以及集合中一些常用高阶函数的API,而每一种集合都使用了不同的数据结构来实现,它们在使用上都有各自的优势。关于数据结构,这篇文章就不展开介绍了,后面会放在单独的专栏里去介绍。下面我们开始本篇文章的讲解。

1.Kotlin中集合的分类

  • List是一个有序的集合,可以存放重复元素,并提供使用索引访问元素的方法
  • Set是一个无序的集合,不允许存放重复元素,null 元素也是唯一的:一个 Set 只能包含一个 null
  • Map则是一种使用键值对的方式来存储数据,其中key是唯一的,每个键都会对应的映射一个value Kotlin标准库提供了基本集合类型的实现。对于ListSetMap等都提供了只读和可操作的接口:
  • 一个 只读 接口,提供访问集合元素的操作
  • 一个 可变 接口,通过写操作扩展相应的只读接口:添加、删除和更新其元素

下面是Kotlin集合接口的示意图:(该图来自Kotlin语言中文站)

2.Collection接口

Collection是集合层次的结构的根。它继承自Iterable接口,该接口只提供集合的只读行为:获取集合的大小、检测是否包含某个成员等等。具体的结构如下代码:

kotlin 复制代码
public interface Collection<out E> : Iterable<E> {

    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
    
}

该接口主要的实现类是ListSet,在Kotlin的内置API中为我们提供了快速构建集合的顶层函数listOfsetOf(),如下代码示例:

kotlin 复制代码
fun main() {
    val listCar = listOf("CAR", "BUS", "SUV")
    printAll(listCar)

    val setCar = setOf("CAR", "BUS", "SUV")
    printAll(setCar)
}

private fun printAll(collection:Collection<String>) {
    println(collection)
}

// 输出
[CAR, BUS, SUV]
[CAR, BUS, SUV]

Collection接口中并没有提供任何可写的操作方法,所以这里我们所创建的List它只包含了该集合中的一些只读信息,例如sizeisEmpty()

2.MutableCollection接口

MutableCollection是一个具有写操作的Collection接口,它继承Collection,其具体的代码如下:

kotlin 复制代码
public interface MutableCollection<E> : Collection<E>, MutableIterable<E> {

    override fun iterator(): MutableIterator<E>

    public fun add(element: E): Boolean

    public fun remove(element: E): Boolean

    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
    
}

mutable翻译成中文的意思是可用的,MutableCollection就是可变的集合。对于我们在Kotlin中使用mutableListof()mutableSetOf()函数创建的集合就是可更改的。我们可以对其中存储的数据类型,进行add()remove()等操作,如下代码示例:

kotlin 复制代码
fun main() {
    val listCar = mutableListOf("CAR", "BUS", "SUV")
    listCar.removeAt(0)
    listCar.add("MARS ONE")
    println(listCar)

    val setCar = mutableSetOf("CAR", "BUS", "SUV")
    setCar.remove("CAR")
    setCar.add("MARS ONE")
    println(setCar)
}

// 输出
[BUS, SUV, MARS ONE]
[BUS, SUV, MARS ONE]

3.Map

Map以键值对的方式来存储数据;其中键是唯一的,不同的键可以有相同的值。Map接口提供特定的函数通过键来访问值、搜索键和值等操作。使用mapof()函数创建一个只读的map:

vbnet 复制代码
val mapCar = mapOf("0" to "CAR", "1" to "BUS", "2" to "SUV")

mapOf()函数接收一个可变的Pair类型的数组:

kotlin 复制代码
public fun <K, V> mapOf(vararg pair: Pair<K, V>) : Map<K,V>

"0" to "Car"``to是一个顶层的中缀函数,其返回值是Kotlin中内置的数据类Pair

kotlin 复制代码
infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that) 

遍历mapCarkey

scss 复制代码
for (key in mapCar.keys) {
    println("key = $key")
}

遍历mapCarvalue

scss 复制代码
for (value in mapCar.values) {
    println("value = $value")
}

遍历mapCarentries

bash 复制代码
for (entry in mapCar.entries) {
    println("key = ${entry.key}, value = ${entry.value}")
}

使用解构声明遍历mapCar

kotlin 复制代码
for ((key, value) in mapCar) {
    println("$key : $value")
}

(key, value) 解构的是一个Map.Entry<String,String>对象

4.MutableMap

MutableMap是一个具有写操作的Map接口,可以向其添加新的键值对:

kotlin 复制代码
fun main() {
    val mapCar = mutableMapOf("0" to "CAR", "1" to "BUS", "2" to "SUV")
    mapCar["1"] = "BUS"
    mapCar.put("2","SUV")
    for ((key, value) in mapCar) {
        println("key = $key, value = $value")
    }
}
//  输出
key = 0, value = CAR
key = 1, value = BUS
key = 2, value = SUV

这里我们再次使用了中缀函数to()创建了短时存活的Pair对象,因此建议仅在性能不重要时才使用它。 为避免过多的内存使用,请使用其他方法。例如,可以创建可写 Map 并使用写入操作填充它,apply()函数可以帮助保持初始化流畅:

kotlin 复制代码
val numbersMap = mutableMapOf<String, String>().apply { 
this["one"] = "1"
this["two"] = "2"
}

这里我们又使用了mapCar["1"] = "BUS"这种方式来存储键值对,我们选中[]操作符点进去就可以看到其具体的实现如下代码:

kotlin 复制代码
public inline operator fun <K, V> MutableMap<K, V>.set(key: K, value: V): Unit {
    put(key, value)
}

可以看到[]操作符是由MutableMap的扩展函数set()来进行重载的。

5.空集合函数

Kotlin内置API中为我们常用的集合都提供了空集合函数,如果你想要快速的创建一个空集合函数,就可以使用如下方式:

ini 复制代码
val emptyList = emptyList<String>()
val emptySet = emptySet<String>()
val emptyMap = emptyMap<String, String>()

6.filter()函数

filter()函数是Iterable接口中一个具有代表性的扩展函数,通过Lambda表达式中的参数来构造条件,过滤其中满足条件的结果集。下面我们先来看下其具体的实现:

kotlin 复制代码
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
    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) if (predicate(element)) destination.add(element)
    return destination
}

filter()函数是Iterable的一个扩展函数,其拥有一个函数类型的参数predicate,而(T) -> Boolean表示我们的函数类型接受一个T类型的参数,并返回一个Boolean类型的值。接着在filter()函数内部我们调用了filterTo()函数,该函数也是Iterable接口的一个扩展函数,同时也是一个泛型函数,它也拥有一个和filter()函数相同类型的函数类型参数predicate,并返回一个新的List

filterTo()函数内部,我们使用for (element in this)遍历了该集合,并通过if (predicate(element))条件来判断符合条件的element,然后将其添加到新的集合中。而我们在调用filter()函数时,其初始化函数类型参数实例的Lambda表达式中的参数正是这里传入的element,然后我们的条件也是通过这个element参数构建的,其返回值是一个布尔类型。

掌握了实现方式,我们再来看一个简单应用:

kotlin 复制代码
fun main() {
    val numbers = listOf("one", "two", "three", "four")
    val filterList = numbers.filter { it.length > 3 }
    println(filterList)
}

// 输出
[three, four]

从输出结果中我们可以看到,我们成功的使用了elementit.length > 3作为判断条件,然后返回了一个符合过滤条件的新集合filterList

6.map()函数

map()函数在实现上和filter()是有些类似的,但是其结果是完全不同的,因为map函数中的函数类型参数,返回的值不再是一个布尔类型。下面我们就来看下map()函数的具体实现:

kotlin 复制代码
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    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)
        destination.add(transform(item))
    return destination
}

首先我们来分析一下map()函数,在map()函数中我们定义个了一个类型为(T) -> R的函数类型参数transformmap()函数的返回值类型是List<R>, 看到这里我想读者应该能明白map()函数的真实用意了,就是我们将原本拥有类型参数T的集合List<T>,然后将List<T>中的每一个item通过某种转换关系,转换成一个新的类型R,再返回一个新的集合List<R>

接着我们再来分析一下mapTo()函数,这个函数涉及到泛型的知识比较多,看其实现理解其中的意思其实并不难,但是想把这个函数中泛型的运用理解清楚就不是那么容易了,不过不着急,下面我们就来一步一步的来分析这个泛型的扩展函数。

在函数mapTo()中我们定义了三个类型参数分别是 T, R, C 前两个类型参数到比较好理解,但是第三个类型参数C却不是那么容易理解。

swift 复制代码
C : MutableCollection<in R>

在上面的filterTo()函数中该类型参数也是这么声明。这里我们就来详细的分析一下C这个类型参数,可以看到这里将C的上界定义为MutableCollection<in R>,那么C则是一个受限的类型参数。而MutableCollection是一个泛型接口,在其声明的泛型中是不协变的。而这里使用到了,我们在上一篇文章中介绍的内容类型投影,也就是使用处形变。假设我们有下面示例中的一个方法normal(),该方法同样拥有一个MutableCollection类型的参数:

这里我们利用使用处形变的特性,让MutableCollection<Person>成为了MutableCollection<Student>的子类,但是在使用处要注意这里的类型转换的问题。下面我们就来写一个简单的示例,来看一下map()函数:

kotlin 复制代码
fun main() {
    val list = listOf(1, 2, 3)
    val newList = list.map { it.toString() }
    println(newList)
}

// 输出
[1, 2, 3]

总结

关于Kotlin中所提供的集合的方法有很多,这里只是提供了两个比较常用的方法来分析。而这些方法只要在我们熟练的掌握了Kotlin的语法基础知识之后,都可以很快的分析出其意图。

写到这里我们的Kotlin语法基础篇,也算是快接近尾声了,后面笔者打算再介绍下Kotlin中的一些特别的语法和特性,以及一些和Java中改动比较明显的小知识点。好了,到这里这篇关于Kotlin集合的文章就介绍完了。

2023年的最后一天了,希望2024年可以带了更多的文章来提升自己。加油,加油,再加油~

相关推荐
王哲晓2 分钟前
第三十章 章节练习商品列表组件封装
前端·javascript·vue.js
萌面小侠Plus3 分钟前
Android笔记(三十三):封装设备性能级别判断工具——低端机还是高端机
android·性能优化·kotlin·工具类·低端机
慢慢成长的码农4 分钟前
Android Profiler 内存分析
android
大风起兮云飞扬丶4 分钟前
Android——多线程、线程通信、handler机制
android
fg_4116 分钟前
无网络安装ionic和运行
前端·npm
理想不理想v7 分钟前
‌Vue 3相比Vue 2的主要改进‌?
前端·javascript·vue.js·面试
L725610 分钟前
Android的Handler
android
清风徐来辽11 分钟前
Android HandlerThread 基础
android
酷酷的阿云17 分钟前
不用ECharts!从0到1徒手撸一个Vue3柱状图
前端·javascript·vue.js
微信:1379712058719 分钟前
web端手机录音
前端