第5章 函数与函数式编程java
凡此变数中函彼变数者,则此为彼之函数。 ( 李善兰《代数学》)编程
函数式编程语言最重要的基础是λ演算(lambda calculus),并且λ演算的函数能够传入函数参数,也能够返回一个函数。函数式编程 (简称FP) 是一种编程范式(programming paradigm)。数组
函数式编程与命令式编程最大的不一样是:函数式编程的焦点在数据的映射,命令式编程(imperative programming)的焦点是解决问题的步骤。函数式编程不单单指的是Lisp、Haskell、 Scala等之类的语言,更重要的是一种编程思惟,解决问题的思考方式,也称面向函数编程。数据结构
函数式编程的本质是函数的组合。例如,咱们想要过滤出一个List中的奇数,用Kotlin代码能够这样写编程语言
package com.easy.kotlin fun main(args: Array<String>) { val list = listOf(1, 2, 3, 4, 5, 6, 7) println(list.filter { it % 2 == 1 }) // lambda表达式 }
这个映射的过程可使用下面的图来形象化地说明函数式编程
而一样的逻辑咱们使用命令式的思惟方式来写的话,代码以下函数
package com.easy.kotlin; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import static java.lang.System.out; public class FilterOddsDemo { public static void main(String[] args) { List<Integer> list = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7}); out.println(filterOdds(list)); // 输出:[1, 3, 5, 7] } public static List<Integer> filterOdds(List<Integer> list) { List<Integer> result = new ArrayList(); for (Integer i : list) { if (isOdd(i)) { result.add(i); } } return result; } private static boolean isOdd(Integer i) { return i % 2 != 0; } }
咱们能够看出,函数式编程是简单天然、直观易懂且美丽优雅的编程风格。函数式编程语言中一般都会提供经常使用的map、reduce、filter等基本函数,这些函数是对List、Map集合等基本数据结构的经常使用操做的高层次封装,就像一个更加智能好用的工具箱。工具
函数式编程是关于不变性和函数组合的编程范式。函数式编程有以下特征测试
Kotlin中使用 fun 关键字来声明函数,其语法实例以下图所示spa
为了更加直观的感觉到函数也能够当作变量来使用,咱们声明一个函数类型的变量 sum 以下
>>> val sum = fun(x:Int, y:Int):Int { return x + y } >>> sum (kotlin.Int, kotlin.Int) -> kotlin.Int
咱们能够看到这个函数变量 sum 的类型是
(kotlin.Int, kotlin.Int) -> kotlin.Int
这个带箭头( -> )的表达式就是一个函数类型,表示一个输入两个Int类型值,输出一个Int类型值的函数。咱们能够直接使用这个函数字面值 sum
>>> sum(1,1) 2
从上面的这个典型的例子咱们能够看出,Kotlin也是一门面向表达式的语言。既然 sum 是一个表明函数类型的变量,稍后咱们将看到一个函数能够当作参数传入另外一个函数中(高阶函数)。
固然,咱们仍然能够像C/C++/Java中同样,直接带上函数名来声明一个函数
fun multiply(x: Int, y: Int): Int { return x * y } multiply(2, 2) // 4
咱们在本章开头部分讲到了这段代码
val list = listOf(1, 2, 3, 4, 5, 6, 7) list.filter { it % 2 == 1 }
这里的filter函数的入参 { it % 2 == 1 } 就是一段 lambda表达式。实际上,由于filter函数只有一个参数,全部括号被省略了。因此,filter函数调用的完整写法是
list.filter ({ it % 2 == 1 })
其中的filter函数声明以下
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T>
其实,filter函数的入参是一个函数 predicate: (T) -> Boolean 。 实际上,
{ it % 2 == 1 }
是一种简写的语法,完整的lambda表达式是这样写的
{ it -> it % 2 == 1 }
若是拆开来写,就更加容易理解
>>> val isOdd = { it: Int -> it % 2 == 1 } // 直接使用lambda表达式声明一个函数,这个函数判断输入的Int是否是奇数 >>> isOdd (kotlin.Int) -> kotlin.Boolean // isOdd函数的类型 >>> val list = listOf(1, 2, 3, 4, 5, 6, 7) >>> list.filter(isOdd) // 直接传入isOdd函数 [1, 3, 5, 7]
其实,在上面的代码示例 list.filter(isOdd) 中,咱们已经看到了高阶函数了。如今咱们再添加一层映射逻辑。咱们有一个字符串列表
val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg")
而后,咱们想要过滤出字符串元素的长度是奇数的列表。咱们把这个问题的解决逻辑拆成两个函数来组合实现
val f = fun (x: Int) = x % 2 == 1 // 判断输入的Int是否奇数 val g = fun (s: String) = s.length // 返回输入的字符串参数的长度
咱们再使用函数 h 来封装 “字符串元素的长度是奇数” 这个逻辑,实现代码以下
val h = fun(g: (String) -> Int, f: (Int) -> Boolean): (String) -> Boolean { return { f(g(it)) } }
可是,这个 h 函数的声明未免有点太长了。尤为是3个函数类型声明的箭头表达式,显得不够简洁。不过不用担忧。
Kotlin中有简单好用的 Kotlin 类型别名, 咱们使用 G,F,H 来声明3个函数类型
typealias G = (String) -> Int typealias F = (Int) -> Boolean typealias H = (String) -> Boolean
那么,咱们的 h 函数就可简单优雅的写成下面这样了
val h = fun(g: G, f: F): H { return { f(g(it)) } // 须要注意的是,这里的 {} 是不能省略的 }
这个 h 函数的映射关系可用下图说明
函数体中的这句代码 return { f(g(it)) } , 这里的 {} 它表明这是一个lambda表达式,返回的是一个 (String) -> Boolean 函数类型。若是没有 { } , 那么返回值就是一个布尔类型Boolean了。
经过上面的代码例子,咱们能够看到,在Kotlin中,咱们能够简单优雅的实现高阶函数。OK,如今逻辑已经实现完了,下面咱们在 main 函数中运行测试一下效果。
fun main(args: Array<String>) { val strList = listOf("a", "ab", "abc", "abcd", "abcde", "abcdef", "abcdefg") println(strList.filter(h(g, f))) // 输出:[a, abc, abcde, abcdefg] }
当你看到 h(g, f) 这样的复合函数的代码时,你必定很开心,感到很天然,这跟数学公式真是很贴近,简单易懂。