### 扁平化——处理嵌套集合: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**。
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android