🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### 扁平化——处理嵌套集合:flatMap、flatten #### flatten 有些时候,我们遇到的集合元素不仅仅是数值、类、字符串这种类型,也可能是集合。(紧接着filter函数章节的person数据类)比如: ``` val list = listOf(listOf(jilen, shaw, lisa), listOf(yison, pan), listOf(jack)) ``` 上面就是一个嵌套集合。这种集合我们在业务中会经常碰到,但是大多数时候,我们**都希望嵌套集合中各个元素能够被拿出来,然后组成一个只有这些元素的集合**,就像这样: ``` val newList = listOf(jilen, shaw, lisa, yison, pan, jack) ``` 那么我们怎样根据嵌套集合来获得这样一个集合呢?Kotlin又提供给我们了一个非常棒的API—flatten。 ``` >>> list.flatten() [PersonData(name=Jilen, age=30, sex=m, score=85), PersonData(name=Shaw, age=18, sex=m, score=90), PersonData(name=Lisa, age=25, sex=f, score=88), PersonData(name=Yison, age=40, sex=f, score=59), PersonData(name=Pan, age=36, sex=f, score=55), PersonData(name=Jack, age=30, sex=m, score=70)] ``` 通过**使用flatten,我们就实现了对嵌套集合的扁平化**。其实flatten方法的原理很简单,我们来看看它的实现源码: ~~~ public fun <T> Iterable<Iterable<T>>.flatten(): List<T> { val result = ArrayList<T>() for (element in this) { result.addAll(element) } return result } ~~~ **首先声明一个数组result,该数组的长度是嵌套集合中所有子集合的长度之和。然后遍历这个嵌套集合,将每一个子集合中的元素通过addAll方法添加到result中。最终就得到了一个扁平化的集合**。 #### flatMap 假如我们**并不是想直接得到一个扁平化之后的集合,而是希望将子集合中的元素“加工”一下,然后返回一个“加工”之后的集合。比如我们要得到一个由姓名组成的列表**,应该如何去做呢?Kotlin还给我们提供了一个方法——flatMap,可以用它实现这个需求: ``` >>> list.flatMap {it.map{it.name}} [Jilen, Shaw, Lisa, Yison, Pan, Jack] ``` flatMap接收了一个函数,该函数的返回值是一个列表,一个由学生姓名组成的列表。我们先不解释flatMap是如何工作的,先来看看能不能利用其他方法也实现上面的需求。之前我们学习过flatten方法和map方法,将这两个方法结合起来使用一下: ``` >>> list.flatten().map {it.name} [Jilen, Shaw, Lisa, Yison, Pan, Jack] ``` 通过这个例子你会发现,**flatMap好像就是先将列表进行flatten操作然后再进行map操作,而且上面这种方式似乎比使用flatMap要更加直观一些**。让我们带着疑惑进入接下来的这个场景: ``` data class Student (val name: String, val age: Int, val sex: String, val score: Int, val hobbies: List<String>) ``` 我们给学生类加了一个属性:hobbies,用来表示学生的兴趣爱好。一个学生的爱好可能有许多种: ``` val jilen = Student("Jilen", 30, "m", 85, listOf("coding", "reading")) val shaw = Student("Shaw", 18, "m", 90, listOf("drinking", "fishing")) val yison = Student("Yison", 40, "f", 59, listOf("running", "game")) val jack = Student("Jack", 30, "m", 70, listOf("drawing")) val lisa = Student("Lisa", 25, "f", 88, listOf("writing")) val pan = Student("Pan", 36, "f", 55, listOf("dancing")) val students = listOf(jilen, shaw, yison, jack, lisa, pan) ``` 那现在需要从学生列表中取出学生的爱好,然后将这些爱好组成一个列表。先看看使用flatten和map怎么去做: ``` >>> students.map {it.hobbies}.flatten() [coding, reading, drinking, fishing, running, game, drawing, writing, dancing] ``` 然后使用flatMap实现: ``` >>> students.flatMap {it.hobbies} [coding, reading, drinking, fishing, running, game, drawing, writing, dancing] ``` 通过这个例子我们又发现,**flatMap是先将列表进行map操作然后再进行flatten操作的,而且这个例子中使用flatMap要更加简洁**。 flatMap 函数做了两件事情:首先根据作为实参给定的函数对集合中的每个元素做变换(或者说映射),然后把多个列表合并(或者说平铺〉成一个列表,下面通过另一个例子更加直观地了解 ``` fun main(args: Array<String>) { val strings = listOf("abc", "def") println(strings.flatMap { it.toList() })//[a, b, c, d, e, f] } ``` ![](https://img.kancloud.cn/e3/39/e3395f000b564e65118b28a82b98595a_378x279.png) 字符串上的toList 函数把它转换成字符列表。如果和toList一起使用的是map 函数,你会得到一个字符列表的列表,就如同图中的第二行。flatMap 函数还会执行后面的步骤,并返回一个包含所有元素(字符)的列表。 #### flatMap源码 那么flatMap究竟是如何工作的呢,同样来看看它的实现源码: ~~~ public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> { return flatMapTo(ArrayList<R>(), transform) } ~~~ 可以看到,flatMap接收一个函数:transform。 ``` transform: (T) -> Iterable<R> ``` **transform函数接收一个参数(该参数一般为嵌套列表中的某个子列表),返回值为一个列表**。比如我们前面用flatMap获取爱好列表时: ``` { it.hobbies } // 上面的表达式等价于: { it -> it.hobbies } ``` 在flatMap中调用了一个叫作flatMapTo的方法,该方法就是实现flatMap的主要方法。该方法的源码如下: ~~~ public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C { for (element in this) { val list = transform(element) destination.addAll(list) } return destination } ~~~ flatMapTo接收两个参数,一个参数为一个列表,该列表为一个空的列表,另外一个参数为一个函数,该函数的返回值为一个序列。**flatMapTo的实现很简单,首先遍历集合中的元素,然后将每个元素传入函数transform中得到一个列表,然后将这个列表中的所有元素添加到空列表destination中,这样最终就得到了一个经过transform函数处理过的扁平化列表**。 **flatMap其实可以看作由flatten和map进行组合之后的方法,组合方式根据具体情况来定。当我们仅仅需要对一个集合进行扁平化操作的时候,使用flatten就可以了;如果需要对其中的元素进行一些“加工”,那我们可以考虑使用flatMap**。