kotlin.collections.Maps.kt 文件定义了许多针对 Map 接口的工具函数,其中大多数是涉及函数式编程的高阶函数,我们在这里先不讲,等到集合框架结束后进入高阶函数时再讲。今天只讲几个简单的函数。
Pair 类和 to() 函数
Map 接口表示一种“键 -> 值”的数据结构,一个键对应一个值。但在具体实现中,Map 并没有维护键和值两个结构,而是一个个 Entry 对象,每个 Entry 对象表示一个键与值的对应关系,它有 3 个属性:key、value 和 hash,因为 hash 只与 key 有关,所以可以视为只有两个属性:key 和 value。
Kotlin 为简化 Map 的创建,定义了一个新类:Pair,它相当于简化版的 Map.Entry 类:
// kotlin.Tuple.ktdata class Pair(
val first: A,
val second: B
) : Serializable {
override fun toString() = "($first, $second)"
}
嗯,我们复习一下数据类,请问这个 Pair 类封装了哪些函数呢?
答案是 getFirst()、getSecond()、component1()、component2()、copy()、hashCode()、equals() 函数。
这里的 first 相当于 key、second 相当于 value,hashCode() 充当了 hash 属性。
有了 Pair 类还不够,Kotlin 还定义了一个 to 函数来创建 Pair 对象:
// kotlin.Tuple.ktinfix fun A.to(that: B): Pair = Pair(this, that)
这是一个用 infix 修饰的中缀函数,也是一个 A 类对象的扩展函数,它接受一个 B 类的参数,使用被扩展的 A 类对象和作为参数的 B 类对象创建一个 Pair 对象。于是我们就能这样写了:
val p1 = 1 to "Hello"
// 等价于 val p1 = 1.to("Hello")val p2 = p1 to "World"
// p1: Pair// p2: Pair, String>
mapOf() 与 mutableMapOf() 函数
与 List 和 Set 相似,Kotlin 的 Map 接口也提供了生成不可变 Map 的 mapOf() 函数和生成可变 Map 的 mutableMapOf() 函数,不过与前两者不同的是,Map 接口的两个函数 只接受 Pair 对象作为参数:
inline fun mapOf(): Map = emptyMap()
fun mapOf(pair: Pair): Map =
java.util.Collections.singletonMap(pair.first, pair.second)
fun mapOf(vararg pairs: Pair): Map =
if (pairs.size > 0) linkedMapOf(*pairs) else emptyMap()
inline fun mutableMapOf(): MutableMap = LinkedHashMap()
fun mutableMapOf(vararg pairs: Pair): MutableMap =
LinkedHashMap(mapCapacity(pairs.size)).apply { putAll(pairs) }
可以注意这几个细节:mapOf() 和 mutableMapOf() 在元素数大于 1 时,都采用 LinkedHashMap 的实现方式,这与 setOf() 和 mutableSetOf() 函数采用 LinkedHashSet 的实现是相似的。此外,因为 LinkedHashSet 只是对 LinkedHashMap 的简单包装,可以认为这四个函数都采用了相似的实现方式。
linkedMapOf(*pairs) 是什么写法?别以为这是 C 语言的指针呀,这叫 展开操作符,一般来说,我们给 vararg 函数传参数时,只能一个一个地传,如果想把一个数组传给 vararg 参数,可以在数组前面加个 *,告诉编译器:“我不是把数组对象作为参数,而是把数组里的每个元素作为参数”,可以这样写:
val array = intArrayOf(1, 2, 3)
val list = listOf(0, *array, 4, 5)
// list: List = [0, 1, 2, 3, 4, 5]
这里用到了改进的 putAll() 函数,下面会讲到。
hashMapOf() 与 linkedMapOf() 函数
与 List 和 Set 接口几个具体实现类函数相似,hashMapOf() 和 LinkedMapOf() 函数创建的都是 可变 Map:
inline fun hashMapOf(): HashMap = HashMap()
fun hashMapOf(vararg pairs: Pair): HashMap =
HashMap(mapCapacity(pairs.size)).apply { putAll(pairs) }
inline fun linkedMapOf(): LinkedHashMap = LinkedHashMap()
fun linkedMapOf(vararg pairs: Pair): LinkedHashMap =
LinkedHashMap(mapCapacity(pairs.size)).apply { putAll(pairs) }
解构函数
我们之前讲到了,所谓解构函数就是用 operator 关键字修饰的 componentN() 函数,这里的 N 是从 1 到 5 的整数。Maps.kt 中定义了两个解构函数:
inline operator fun Map.Entry.component1(): K = key
inline operator fun Map.Entry.component2(): V = value
它们都是 Map.Entry 的扩展函数,分别返回这个 Entry 的键和值,常用来遍历 Map 的键和值:
val map = mapOf(1 to 2, 3 to 4)
for((key, value) in map) {
println("$key -> $value")
}
// 1 -> 2// 3 -> 4
这个 for 循环会遍历 map 的每一个 Entry,调用解构函数分别把 Entry 的键和值赋给临时变量 key 和 value,然后用 key 和 value 执行我们定义的循环体。
plus() 与 minus() 函数
Map.kt 重载了操作符 + 和 -,方便我们增删元素。因为重载函数实在太多,这里就不列出来了,总结几个点吧:所有 plus() 和 minus() 函数都是 不可变 的 Map 的扩展函数,所以每次调用 plus() 或 minus() 函数都会产生一个新的 Map,这在 Map 内元素很多时可能会产生性能问题。
plus() 和 minus() 函数有以下几种参数不同的重载形式:Array>:Pair 数组;
Iterable>:Pair 集合;
Pair:单个 Pair 对象;
Sequence>:Pair 序列(Kotlin 添加的新集合形式)
Map:另一个 Map;
对于 MutableMap,Maps.kt 重载了 +=(plusAssign() 函数)和 -=(minusAssign() 函数)操作符,分别相当于调用 put() / putAll() 函数和 remove() / removeAll() 函数:
val mutableMap = mutableMapOf(1 to 2, 3 to 4)
mutableMap += arrayOf(1 to 2, 5 to 6)
// mutableMap = [1->2, 3->4, 5->6]mutableMap -= 3
// mutableMap = [1->2, 5->6]
toMap() 函数
Maps.kt 还提供了一系列 toMap() 函数,用来将其它数据形式的 Pair 对象转换为 可变 的 Map,包括下面两套重载形式:支持 Array>、Iterable>、Sequence> 和 Map 四种数据结构转换为 Map;
支持无参和一个 destination 参数两种形式,前者直接将转换得到的 Map 赋给自身,后者则会把转换成的 Map 赋给 destination。
改进的 putAll() 函数
Maps.kt 给 MutableMap 重载了三个新的 putAll() 函数,它们分别接受 Array>、Iterable> 和 Sequence> 对象。