前言
在上一篇文章中我们详细的介绍了Kotlin
中by
关键字的使用。这篇文章我们将介绍Kotlin
中集合的使用以及集合中一些常用高阶函数的API
,而每一种集合都使用了不同的数据结构来实现,它们在使用上都有各自的优势。关于数据结构,这篇文章就不展开介绍了,后面会放在单独的专栏里去介绍。下面我们开始本篇文章的讲解。
1.Kotlin中集合的分类
List
是一个有序的集合,可以存放重复元素,并提供使用索引访问元素的方法Set
是一个无序的集合,不允许存放重复元素,null
元素也是唯一的:一个Set
只能包含一个null
Map
则是一种使用键值对的方式来存储数据,其中key
是唯一的,每个键都会对应的映射一个value
Kotlin
标准库提供了基本集合类型的实现。对于List
、Set
、Map
等都提供了只读和可操作的接口:- 一个 只读 接口,提供访问集合元素的操作
- 一个 可变 接口,通过写操作扩展相应的只读接口:添加、删除和更新其元素
下面是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
}
该接口主要的实现类是List
和Set
,在Kotlin
的内置API
中为我们提供了快速构建集合的顶层函数listOf
、setOf()
,如下代码示例:
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
它只包含了该集合中的一些只读信息,例如size
、isEmpty()
。
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)
遍历mapCar
的key
:
scss
for (key in mapCar.keys) {
println("key = $key")
}
遍历mapCar
的value
:
scss
for (value in mapCar.values) {
println("value = $value")
}
遍历mapCar
的entries
:
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
的函数类型参数transform
,map()
函数的返回值类型是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年可以带了更多的文章来提升自己。加油,加油,再加油~