Kotlin
的官方扩展函数一直给我的感觉就是:简单、好用还呈现一种“只有你想不到没有你找不到”的态势。
今天咱不聊多么复杂的,就来谈谈 map
和 flatMap
,名字很像,到底这两个货有什么区别呢?分别面对的又是什么使用场景?
首先,名字即功能,map
和 flatMap
是为实现“映射”而存在的,官网是这么描述映射的:
The mapping transformation creates a collection from the results of a function on the elements of another collection.
意思就是:提供一个转换函数,用以把一个集合的元素转化生成另一个集合,而其中最为基础的就是 map
。
其次,这两个不是单属于某个特定类型的扩展,它们写出来就是面向所有可能需要有“映射”需求的地方的。不信来看看:
// 数组扩展
public inline fun Array.map(transform: (T) -> R): List {
return mapTo(ArrayList(size), transform)
}
// 迭代类扩展
public inline fun Iterable.map(transform: (T) -> R): List {
return mapTo(ArrayList(collectionSizeOrDefault(10)), transform)
}
// 映射扩展
public inline fun Map.map(transform: (Map.Entry) -> R): List {
return mapTo(ArrayList(size), transform)
}
复制代码
都是泛型实现,涵盖的类型可真不少,甚至还有“映射的映射”这样的。像Iterable
这样的,一扩展,支持的类型那可多了,所有的Collections
类型都是,比如List
、Set
;另外,Kotlin
的 Range 类型也没落下。
fun supportNeverEnough() {
arrayOf().map { }
listOf().map { }
hashMapOf().map { }
setOf().map { }
(0 until 10).map { }
// 好了,不继续写了……
}
复制代码
当然,flatMap
也是一样的。
而且值得注意的是,不管是哪个map,其结果都是List
类型。
map
接受传入的转换函数,处理后,即将源转化成一个新的List,且这个新的List的元素顺序和其源是一致的。
对于“数组”类的源(比如list, set, array等),map
为:
fun XXX.map(transform: (T) -> R): List
复制代码
可以看到,map就是将T类型的集合转化成了R类型的List。
public inline fun Array.map(transform: (T) -> R): List {
return mapTo(ArrayList(size), transform)
}
public inline fun > Array.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
复制代码
map
内部会调用 Array
方法,该方法第一个参数是 MutableCollection
的子类型,即可变集合,用来迭代保存结果元素。
第二个参数就是转换函数,将T类型转为R类型。
内部 for 循环迭代所有元素,每个元素调用转换函数,生成结果并添加至集合,最后返回。整个过程算是十分简单了。
这是针对迭代集合 Iterable
的:
public inline fun Iterable.map(transform: (T) -> R): List {
return mapTo(ArrayList(collectionSizeOrDefault(10)), transform)
}
internal fun Iterable.collectionSizeOrDefault(default: Int): Int = if (this is Collection<*>) this.size else default
public inline fun > Iterable.mapTo(destination: C, transform: (T) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
复制代码
看起来数组的map很类似,但这里多了一个 collectionSizeOrDefault
方法,这是什么用意呢?
很好理解:相当于可以预先设定最终容器的大小。因为有可能此 Iterable
类型不是 Collection
,无法获取 size,所以加了个判断,如果无法获取,则默认一个 size 为 10.
Map
映射的 map
:
public inline fun Map.map(transform: (Map.Entry) -> R): List {
return mapTo(ArrayList(size), transform)
}
public inline fun > Map.mapTo(destination: C, transform: (Map.Entry) -> R): C {
for (item in this)
destination.add(transform(item))
return destination
}
复制代码
因为是 Map
,所以泛型为 Map
的 Entry
。
同样的,flatMap
也支持前面提到的所有类型:
fun supportNeverEnoughForFlatMap() {
arrayOf().flatMap { 0..it }
listOf().flatMap { 0..it.length }
hashMapOf().flatMap { 0..it.value.length }
setOf().flatMap { 0..it }
(0 until 10).flatMap { 0..it }
}
复制代码
和 map
的区别在于,flatMap
的转换函数类型是: transform: (T) -> Iterable
,即输入T类型,得到R类型的迭代集合。
所以说,map转换是一到一,flatMap则是一到多,但是最终,flatMap
得到的还是一个R的集合
public inline fun Array.flatMap(transform: (T) -> Iterable): List {
return flatMapTo(ArrayList(), transform)
}
public inline fun > Array.flatMapTo(destination: C, transform: (T) -> Iterable): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
复制代码
源码看起来和 map
是很像的,关键的不同处在于:
destination.addAll(list)
复制代码
因为映射结果是集合,所以这里调用的是addAll
。虽然一个item得到一个集合,但最后返回值不是集合的集合,仍然是单集合 —— 很绕吗?不,这就是flat这个前缀存在的意义:扁平化。
public inline fun Iterable.flatMap(transform: (T) -> Iterable): List {
return flatMapTo(ArrayList(), transform)
}
public inline fun > Iterable.flatMapTo(destination: C, transform: (T) -> Iterable): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
复制代码
和数组如出一辙,不用细讲了。
public inline fun Map.flatMap(transform: (Map.Entry) -> Iterable): List {
return flatMapTo(ArrayList(), transform)
}
public inline fun > Map.flatMapTo(destination: C, transform: (Map.Entry) -> Iterable): C {
for (element in this) {
val list = transform(element)
destination.addAll(list)
}
return destination
}
复制代码
同样的配方。
但是!!!虽然不细讲,细心的人还是能发现: flatMap
初始化集合全都没指定大小 —— 因为“一到多” 的映射操作,根本无法预估最终的集合大小啊是不?
看到这里,相信 map
和 flatMap
的区别已经很清楚了吧,简单地就如前面所说:二者区别在于转换函数,前者“一到一”,后者“一到多”。而它们的返回类型,都是一模一样的。
作者:王可大虾
链接:https://juejin.cn/post/7112716975230418980
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。