一次性讨论清楚Kotlin的map和flatMap

Kotlin 的官方扩展函数一直给我的感觉就是:简单、好用还呈现一种“只有你想不到没有你找不到”的态势。

今天咱不聊多么复杂的,就来谈谈 mapflatMap,名字很像,到底这两个货有什么区别呢?分别面对的又是什么使用场景?

概述

首先,名字即功能,mapflatMap是为实现“映射”而存在的,官网是这么描述映射的:

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类型都是,比如ListSet;另外,Kotlin 的 Range 类型也没落下。

fun supportNeverEnough() {
    arrayOf().map {  }
    listOf().map {  }
    hashMapOf().map {  }
    setOf().map {  }
    (0 until 10).map {  }
    // 好了,不继续写了……
}
复制代码

当然,flatMap 也是一样的。

而且值得注意的是,不管是哪个map,其结果都是List类型

map

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.mapTo 方法,该方法第一个参数是 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,所以泛型为 , 类似数组和迭代集合,这里只是把迭代转换的参数换成了 MapEntry

flatMap

同样的,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
}
复制代码

同样的配方。

总有个But

但是!!!虽然不细讲,细心的人还是能发现: flatMap 初始化集合全都没指定大小 —— 因为“一到多” 的映射操作,根本无法预估最终的集合大小啊是不?

小结

看到这里,相信 mapflatMap 的区别已经很清楚了吧,简单地就如前面所说:二者区别在于转换函数,前者“一到一”,后者“一到多”。而它们的返回类型,都是一模一样的。

作者:王可大虾
链接:https://juejin.cn/post/7112716975230418980
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

你可能感兴趣的:(Kotlin,kotlin,android,java)