作者简介:CSDN博客专家、华为云·云享专家认证
系列专栏:Kotlin 初学者
一、函数式编程概念
1.1 面向函数编程(FOP)
在函数式编程(FP)中,一切皆是函数。FP是关于不变性和函数组合的一种编程范式。
函数式语言提倡在有限的几种关键数据结构(如list、set、map)上,运用函数的组合(高阶函数)操作,自底向上地来构建世界。Kotlin支持多种编程范式,所以你可以混用面向对象编程和函数式编程范式来解决手头的问题。
1.2 高阶函数
一个函数的参数列表中存在函数类型的参数或是函数的返回值类型为函数类型,那么这个函数就叫做高阶函数。
1.3 为什么使用函数式编程
乍看之下,实现同样的任务,Java版本和函数式版本的代码量差不多,但仔细分析一下,就能看出函数式版本的诸多优势。
- 累加变量(employeeShirtSizes)都是隐式定义的。
- 函数运算结果会自动赋值给累加变量,降低了代码出错的机会。
- 执行新任务的函数很容易添加到函数调用链上,因为他们都兼容lterable类型。
二、函数式编程类别
一个函数式应用通常由三大类函数构成:
- 变换transform。
- 过滤filter
- 合并combine。
每类函数都针对集合数据类型设计,目标是产生一个最终结果。函数式编程用到的函数生来都是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。
2.1 变换transform
变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数,变换每一个元素,然后返回包含已修改元素的集合给链上的其他函数。
最常用的两个变换函数是map和flatMap。
2.1.1 map
map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,会作为链上下一个函数的输入。
map返回的集合中的元素个数和输入集合必须一样,不过,返回的新集合里的元素可以是不同类型的。
fun main() { var citys = listOf("北京", "上海", "广州", "深圳") var chinaCitys = citys .map { citys -> "中国-$citys" } .map { china -> "$china,一线城市!" } //原始集合 println(citys) //修改原始集合元素 println(chinaCitys) }
可以看到,原始集合没有被修改,map变换函数和你定义的变换器函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。
事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。
2.1.2 flatMap
flatMap函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。
//一个集合的集合:List<List<String>> var cityslist = listOf(listOf("北京", "上海", "广州", "深圳"), listOf("合肥","杭州","成都")) //多个集合中的元素合并,返回一个新集合flatCity var flatCity = cityslist.flatMap { it } //原始集合 println(cityslist)//[[北京, 上海, 广州, 深圳], [合肥, 杭州, 成都]] //合并后集合元素 println(flatCity)//[北京, 上海, 广州, 深圳, 合肥, 杭州, 成都]
2.2 过滤filter
过滤是函数式编程的第二大类函数,过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。
- 如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。
- 如果predicate函数返回false,那么受检元素就被移出新集合。
可以理解为按条件筛选元素。
fun main() { var number = listOf(1,3,5,7,12,19,21) //过滤元素大于15的元素,并添加到新集合 var newNumber = number.filter { it>15 } //原始数据 println(number)//[1, 3, 5, 7, 12, 19, 21] //过滤后 println(newNumber)//[19, 21] }
filter+flatMap
对一个集合的集合先合并再过滤。
//flatMap+filter var numberList = listOf( listOf(1, 2, 3, 4, 5), listOf(6, 7, 8, 9, 10), listOf(11, 12, 13, 14, 15) ) //先合并,后过滤 var newNumberList = numberList.flatMap { it.filter { it % 2 == 0 } } println(numberList)//[[1, 2, 3, 4, 5], [6, 7, 8, 9, 10], [11, 12, 13, 14, 15]] println(newNumberList)//[2, 4, 6, 8, 10, 12, 14]
2.3 合并combine
合并是函数式编程的第三大类函数,合并函数能将不同的集合合并成一个新集合,这和接收者是包含集合的集合的flatMap函数不同。
2.3.1 zip
zip合并函数来合并两个集合,返回一个包含键值对的新集合。
1.fun main() { val name = listOf("变换","过滤","合并") val age = listOf(7, 25, 36) val list = name.zip(age) println(list)//[(变换, 7), (过滤, 25), (合并, 36)] val map = list.toMap() println(map)//{变换=7, 过滤=25, 合并=36} println(map["过滤"])//25 }
2.3.2 fold
fold合并函数接受一个初始累加器值,随后会根据匿名函数的结果更新。
fun main() { //初始值为10,将每个元素乘以2相加 var total = listOf(4, 8, 12).fold(10) { total, number -> println("Total:$total") total + (number * 2) } println(total) }
三、序列
- 及早集合(eager collection):List、Set、Map集合的任何一个实例在创建后,它要包含的元素都会被加入并允许你访问。
- 惰性集合〈lazy collection):类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其是用于包含大量元素的集合时,因为集合元素是按需产生的。
Kotlin有个内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。
generateSequence
generateSequence函数接受一个初始种子值作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你指定的迭代器函数,决定下一个要产生的值。
fun main() { //在1-3000的范围内产生1000个能同时整除2和整除7的数 val list = (1..3000).toList().filter { it % 2 == 0 && it % 7 == 0 }.take(1000) println(list.size)//214 //[14, 28, 42, 56, 70, 84, 98, ...2954, 2968, 2982, 2996] println(list) //使用序列,取满足条件的1000个为止 val slist = generateSequence(1, { num -> num + 1 }) .filter { it % 2 == 0 && it % 7 == 0 } .take(1000) .toList() println(slist.size)//1000 //[14, 28, 42, 56, 70, 84, 98, ...13958, 13972, 13986, 14000] println(slist) }