一、函数式编程理解
我们一直在学习面向对象编程范式,另个一个较知名的编程范式是诞生于20世纪50年代,基于抽象数学的λ(lambda)演算发展而来的函数式编程,尽管函数式编程更常用在学术而非软件领域,但它的一些原则适用于任何编程语言。函数式编程范式主要依赖于高阶函数(以函数为参数或返回函数)返回的数据,这些高阶函数专用于处理各种集合,可方便的联合多个同类函数构建链式操作以创建复杂的计算行为。Kotlin支持多种编程范式,所以你可以用混用面向对象编程和函数式编程范式来解决手头问题。
二、函数式编程类别
一个函数式应用通常由三大函数构成:变换transform,过滤filter,合并combine。每类函数都针对数据集合类型设计,目标是产生一个最终结果。函数式编程用到的函数生来就是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。
1.变换函数map
fun main() { val animals = listOf("zebra", "giraffe", "elephant", "rat") /** * map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已经修改的元素的集合 * 会作为链上下一个函数的输入 * * 可以看到,原始集合animals没有被修改,map变换函数和你定义的变换函数做完事情后,返回的是一个新集合 * 这样变量就不用变来变去了。 * 事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。 */ val babies = animals .map { animals -> "A baby $animals" } .map { baby -> "$baby,with the cutest little tail ever!" } println(animals) println(babies) /** * map函数返回的集合中的元素个数和输入集合必须一样,不过,返回的新集合里的元素可以是不同类型的。 * map函数的定义源码:public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> */ val animalsLength = animals.map { it.length } println(animalsLength) }
输出结果如下
[zebra, giraffe, elephant, rat] [A baby zebra,with the cutest little tail ever!, A baby giraffe,with the cutest little tail ever!, A baby elephant,with the cutest little tail ever!, A baby rat,with the cutest little tail ever!] [5, 7, 8, 3]
2、变换函数flatMap
fun main() { /** * flatMap函数操作一个集合中的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。 */ val result = listOf(listOf(1, 2, 3), listOf(4, 5, 6)).flatMap { it } println(result) /** * 变换 * 变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数 * 然后返回已经包含已修改元素的集合给链上的其他函数。 * 最常用的两个变换函数时map和flatMap */ }
输出结果如下
[1, 2, 3, 4, 5, 6]
3、过滤函数filter
fun main() { /** * 过滤是函数式编程的第二大函数,过滤函数接受一个predicate函数,用它按给定条件 * 检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true * 受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么 * 受检元素就被移出新集合。 */ val result = listOf("Jack", "Jimmy", "Rose", "Tom") .filter { it.contains("J") } println(result) val items = listOf( listOf("red apple", "green apple", "blue apple"), listOf("red fish", "green fish"), listOf("yellow banana", "teal banana") ) /** * filter过滤函数接受一个predicate函数,在flatMap遍历它的输入集合中的所有元素时, * filter函数会让predicate函数按过滤条件,将符合条件的元素都放入它返回的新集合里。 * 最后,flatMap会把变换函数返回的子集合合并在一个新集合里。 */ val readItems = items.flatMap { it.filter { it.contains("red") } } println(readItems) }
输出结果如下
[Jack, Jimmy] [red apple, red fish]
4、组合使用filter和map函数找素数
fun main() { /** * 找素数,除了1和它本身,不能被任何数整除的数。仅使用了几个简单的函数, * 我们就解决了找素数这个比较复杂的问题,这就是函数式编程的独特魅力:每个函数 * 做一点,组合起来就能干大事。 * * 除了1和它本身,不能被任何数整除的数 * 取模等于0,说明能够整除,如果没有一个是等于0的,说明是素数 */ val numbers = listOf(7, 4, 8, 4, 3, 33, 18, 11) val primes = numbers.filter { number -> (2 until number).map { number % it }.none { it == 0 } } println(primes) }
输出结果如下
[7, 3, 11]
5、合并函数zip
/** * @Author: ly * @Date: 2023/2/6 * @Description: 合并是函数式编程的第三大类函数,合并函数能将不同的集合合并成一个新集合, * 这和接收者是包含集合的集合的flatMap函数不同 */ fun main() { val employees = listOf("Jack", "Jason", "Tommy") val shirtSize = listOf("large", "x-large", "medium") val ages = listOf(20, 30, 19) /** * zip合并函数来合并两个集合,返回一个包含键值对的新集合 * 让后可以通过toMap()函数,转换为map集合 */ val newList = employees.zip(shirtSize).toMap() val employeesAges = employees.zip(ages).toMap() println(newList["Jack"]) println(employeesAges["Jason"]) }
输出结果如下
large 30
6、合并函数fold
fun main() { /** * fold函数 * 这个合并函数接受一个初始累加器值,随后会根据匿名函数的结果更新。 * 将每个元素值乘以3累加起来 */ val foldedValue = listOf(1, 2, 3, 4).fold(0) { accumulator, number -> println("Accumulator value:$accumulator") accumulator + (number * 3) } println("Final value:$foldedValue") }
输出结果如下
Accumulator value:0 Accumulator value:3 Accumulator value:9 Accumulator value:18 Final value:30
三、为什么要使用函数式编程?
为什么要使用函数式编程
乍看之下,实现同样的任务,Java版本和函数式版本的代码量差不多,但仔细分析一下
就能看出函数式版本的诸多优势。
1.累加变量都是隐式定义的。
2.函数运算结果会自动赋值给累加变量,降低了代码出错的机会。
3.执行新任务的函数很容易添加到函数调用链上,因为他们都兼容Iterable类型。
1、相同的一个需求使用java实现如下
public class FunctionTest { public static void main(String[] args) { List<String> keys = Arrays.asList("Jack", "Jason", "Tommy"); List<String> values = Arrays.asList("large", "x-large", "medium"); HashMap<String, String> hashMap = new HashMap<>(); for (int i = 0; i < keys.size(); i++) { hashMap.put(keys.get(i), values.get(i)); } System.out.println(hashMap); List<String> newLists = new ArrayList<>(); for (Map.Entry<String, String> entry : hashMap.entrySet()) { newLists.add(String.format("%s shirt size %s", entry.getKey(), entry.getValue())); } System.out.println(newLists); } }
输出结果如下
{Jason=x-large, Jack=large, Tommy=medium} [Jason shirt size x-large, Jack shirt size large, Tommy shirt size medium]
2、使用kotlin就很简洁,代码如下
fun main() { val employees = listOf("Jack", "Jason", "Tommy") val shirtSize = listOf("large", "x-large", "medium") /** * zip合并函数来合并两个集合,返回一个包含键值对的新集合 * 让后可以通过toMap()函数,转换为map集合 */ val newList = employees.zip(shirtSize).toMap() /** *为什么要使用函数式编程 * 乍看之下,实现同样的任务,Java版本和函数式版本的代码量差不多,但仔细分析一下 * 就能看出函数式版本的诸多优势。 * 1.累加变量都是隐式定义的。 * 2.函数运算结果会自动赋值给累加变量,降低了代码出错的机会。 * 3.执行新任务的函数很容易添加到函数调用链上,因为他们都兼容Iterable类型。 */ val list = newList.map { "${it.key},shirt size:${it.value}" } println(list) }
输出结果如下
[Jack,shirt size:large, Jason,shirt size:x-large, Tommy,shirt size:medium]