第 5 章 函数式编程 面向对象和面向过程都属于同一类,命令式编程,函数式编程和命令式编程是整体完全不同的思路,命令式编程指的是不管你是拆解对象也好,还是直接按照步骤,按照过程把程序定义好。我们最终落脚点都是计算机具体执行程序的具体命令。函数式编程并不管计算机底层是怎么处理这件事,最关心的是我们整个数据的映射关系是什么样的。 之前的命令式编程不管是面向对象还是面向过程,在做编程设计的时候,我们用到的那些语法,都是面向计算机硬件的抽象,比如在java或者C中,定义一个变量,这个变量是什么概念呢, int a =1; 并不是数学意义上的 a=1;因为数学上a=1;那接下来a和1完全就是等价的关系,而在计算机编程中a=1;之后还可以a=2;还可以在改变他的值,这在数学上是完全不可思议的事情。所以这个a并不是数学意义上的a,是一个变量,对应的是计算机底层的一块内存空间。所以a=1,对应的是计算机底层把一条数据写入到对应的内存空间的一条指令。所以后面再给a赋别的值就是更改内存空间中的数据。所以命令式编程的每一行代码,都能翻译成对应的计算机指令。譬如 if控制语句 for循环都能转换成计算机底层的跳转指令。这对于计算机来讲就是最友好,最容易理解的语言。
但是从另外一方面讲 对于人来说,这可能并不是特别好理,(假如是一个编程小白,没有计算机相关知识,看到这样的表达式就非常的困惑,我们学过数学,但是这里面的等号和数学上的完全不一样,这里面的a也不是数学上的未知数,变量的概念和数学也完全不一样。所以看到这样一种代码可能需要非常清楚编程语言或者计算机底层的逻辑才能把他理解透彻)所以先对应的函数式编程就相当于一道数学题的求解。所以真正的函数式编程语言中,是没有变量的,所有的值来了之后都是常量,都是一个值,a是1他就是1不会在改了。所以在Scala中推荐大家能用常量val的地方就用val,这也是符合函数式编程思想的。把这个定义好之后呢,求解问题,计算的过程就是不停的表达式求值的过程,可能一开始有个a 有个方程要求里面的未知数,未知数相当于一个表达式,我们就是一直再求表达式的值,而且在函数式编程中,每一段程序都有返回值,if else 条件控制 条件分支控制,或者是for循环都有一个返回值,所以真正的函数式编程里面,本质就是一个映射关系,表达式不停的求值,然后做函数的映射关系,不停的y=f(x),不停的给它做转换,这就是函数式编程的本质。所以函数式编程对于大家没有掌握编程的,掌握了数学来讲是更好理解的一种编程方式,并不关心计算机底层的实现,对人更加的友好, 当然与之对应的,对于计算机来讲就不是那么好理解了,所以他的编译器可能要非常的复杂,可能要做非常多额外的操作,才能把他翻译成计算机可以理解的内容。 那对于这两种特性所带来的两种编程范式,命令式编程对计算机来讲更好理解,执行起来效率更高,函数式编程对于人更好理解,执行效率低,但是程序员上手的难度比较低,直接就可以看着代码写出自己的代码,而且在编程过程中可以专注于业务逻辑,不需要考虑过多的计算机底层的处理规则这就是他们最大的区别。 Scala中为什么要借鉴这样的编程范式编程思路呢,一方面Scala设计的时候希望他能是一个更好的语言,能够对于程序员来讲编程效率更高的语言,另一方面,函数式编程有非常好的特性,我们定义出来一个函数之后,其实他的功能就是确定的,函数式编程是拥有不可变性的,比如我们定义一个y = x*x 定义了一个函数,其中的x是可以变化的,对应一个函数的输入参数,对着这个函数而言你可以任意的输入参数,但是只要你输入的是同一个参数比如10 得到的结果永远都是100,对于函数而已他是不可变的,得到的结果都是确定性的,这在函数的处理过程中就可以把他进行分布式的并行处理了,大家自然就想到了,在大数据的场景下,我们一台机器如果处理不过来数据量很大,那就要做一个集群风分布式处理,如果在处理的过程中拥有不可变性,没有变量,所有的值不会受外界的影响,那就可以分布在不同的机器上运行,最后得到的结果汇总即可,彼此直接没有额外的影响,这就是我们说的函数式编程没有副作用的,他特别适合用在大数据处理的分布式处理环境内,提高并行能力的时候使用函数式编程就非常好用,所以Scala1就借鉴了函数式编程的特点,可以说是一门函数式编程语言,所以呢在大数据处理过程当中,经常提到的大数据核心的处理框架Spark底层就用Scala来编写 另外用于构建高并发分布式应用的工具包 azzk底层也是Scala写的当然还有很多耳闻能闲的工具 比如消息队列Kafka底层也是Scala,所以大家会发现这样一种函数式编程的特性再结合了Java一些面对对象的特点,基于JVM可移植性强的特点,最终导致Scala特别容易应用在大数据的应用环境中。这就是函数式编程思想的应用,以及Scala特点的说明,Scala是一门面对对象的语言,同时也是一门函数式编程语言。
1)面向对象编程
Scala 语言是一个完全面向对象编程语言。万物皆对象
对象的本质:对数据和行为的一个封装
2)函数式编程 解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤,解决问题。
例如:请求->用户名、密码->连接 JDBC->读取数据库
5.1 函数基础 5.1.1函数基本语法 1)基本语法
2)案例实操 需求:定义一个函数,实现将传入的名称打印出来。
1 2 3 4 5 6 7 8 9 10 11 12 object TestFunction {def main (args: Array [String ]): Unit = { def f (arg: String ): Unit = { println(arg) } f("hello world" ) } }
5.1.2 函数和方法的区别 1)核心概念
为完成某一功能的程序语句的集合,称为函数。
类中的函数称之方法。
2)案例实操
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package chapter05object Test01_FunctionAndMethod { def main (args: Array [String ]): Unit = { def sayHi (name: String ): Unit = { println("hi, " + name) } sayHi("alice" ) Test01_FunctionAndMethod .sayHi("bob" ) val result = Test01_FunctionAndMethod .sayHello("cary" ) println(result) } def sayHi (name: String ): Unit = { println("Hi, " + name) } def sayHello (name: String ): String = { println("Hello, " + name) return "Hello" } }
5.1.3 函数定义 1)函数定义
函数 1:无参,无返回值
函数 2:无参,有返回值
函数 3:有参,无返回值
函数 4:有参,有返回值
函数 5:多参,无返回值
函数 6:多参,有返回值
2)案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 object Test02_FunctionDefine { def main (args: Array [String ]): Unit = { def f1 (): Unit = { println("1. 无参,无返回值" ) } f1() println(f1()) println("=========================" ) def f2 (): Int = { println("2. 无参,有返回值" ) return 12 } println(f2()) println("=========================" ) def f3 (name: String ): Unit = { println("3:有参,无返回值 " + name) } println(f3("alice" )) println("=========================" ) def f4 (name: String ): String = { println("4:有参,有返回值 " + name) return "hi, " + name } println(f4("alice" )) println("=========================" ) def f5 (name1: String , name2: String ): Unit = { println("5:多参,无返回值" ) println(s"${name1} 和${name2} 都是我的好朋友" ) } f5("alice" ,"bob" ) println("=========================" ) def f6 (a: Int , b: Int ): Int = { println("6:多参,有返回值" ) return a + b } println(f6(12 , 37 )) } }
5.1.4 函数参数1)案例实操 (1)可变参数
(2)如果参数列表中存在多个参数,那么可变参数一般放置在最后
(3)参数默认值,一般将有默认值的参数放置在参数列表的后面
(4)带名参数
2)案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 object Test03_FunctionParameter { def main (args: Array [String ]): Unit = { def f1 (str: String *): Unit = { println(str) } f1("alice" ) f1("aaa" , "bbb" , "ccc" ) def f2 (str1: String , str2: String *): Unit = { println("str1: " + str1 + " str2: " + str2) } f2("alice" ) f2("aaa" , "bbb" , "ccc" ) def f3 (name: String = "atguigu" ): Unit = { println("My school is " + name) } f3("school" ) f3() def f4 (name: String = "atguigu" , age: Int ): Unit = { println(s"${age} 岁的${name} 在尚硅谷学习" ) } f4("alice" , 20 ) f4(age = 23 , name = "bob" ) f4(age = 21 ) } }
5.1.5 函数至简原则(重点) 函数至简原则:能省则省
1)至简原则细节 (1)return 可以省略,Scala 会使用函数体的最后一行代码作为返回值
(2)如果函数体只有一行代码,可以省略花括号
(3)返回值类型如果能够推断出来,那么可以省略(:和返回值类型一起省略)
(4)如果有 return,则不能省略返回值类型,必须指定
(5)如果函数明确声明 unit,那么即使函数体中使用 return 关键字也不起作用
(6)Scala 如果期望是无返回值类型,可以省略等号
(7)如果函数无参,但是声明了参数列表,那么调用时,小括号,可加可不加
(8)如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
(9)如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略
2)案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 package chapter05object Test04_Simplify { def main (args: Array [String ]): Unit = { def f0 (name: String ): String = { return name } println(f0("atguigu" )) println("==========================" ) def f1 (name: String ): String = { name } println(f1("atguigu" )) println("==========================" ) def f2 (name: String ): String = name println(f2("atguigu" )) println("==========================" ) def f3 (name: String ) = name println(f3("atguigu" )) println("==========================" ) println("==========================" ) def f5 (name: String ): Unit = { return name } println(f5("atguigu" )) println("==========================" ) def f6 (name: String ) { println(name) } println(f6("atguigu" )) println("==========================" ) def f7 (): Unit = { println("atguigu" ) } f7() f7 println("==========================" ) def f8 : Unit = { println("atguigu" ) } f8 println("==========================" ) def f9 (name: String ): Unit = { println(name) } (name: String ) => { println(name) } println("==========================" ) } }
5.2 函数高级5.2.1 匿名函数 1)说明 没有名字的函数就是匿名函数。
(x:Int)=>{函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
2)案例实操 需求 1:传递的函数有一个参数
传递匿名函数至简原则:
(1)参数的类型可以省略,会根据形参进行自动的推导
(2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
(3)匿名函数如果只有一行,则大括号也可以省略
(4)如果参数只出现一次,则参数省略且后面参数可以用_代替
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 object Test05_Lambda { def main (args: Array [String ]): Unit = { val fun = (name: String ) => { println(name) } fun("atguigu" ) println("========================" ) def f (func: String => Unit ): Unit = { func("atguigu" ) } f(fun) f((name: String ) => { println(name) }) println("========================" ) f((name) => { println(name) }) f( name => { println(name) }) f( name => println(name) ) f( println(_) ) f( println ) }
需求 2:实际示例,定义一个”二元运算“函数,只操作1和2两个数,但是具体运算通过参数传入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def dualFunctionOneAndTwo (fun: (Int , Int )=>Int ): Int = { fun(1 , 2 ) } val add = (a: Int , b: Int ) => a + bval minus = (a: Int , b: Int ) => a - bprintln(dualFunctionOneAndTwo(add)) println(dualFunctionOneAndTwo(minus)) println(dualFunctionOneAndTwo((a: Int , b: Int ) => a + b)) println(dualFunctionOneAndTwo((a: Int , b: Int ) => a - b)) println(dualFunctionOneAndTwo((a, b) => a + b)) println(dualFunctionOneAndTwo( _ + _)) println(dualFunctionOneAndTwo( _ - _)) println(dualFunctionOneAndTwo((a, b) => b - a)) println(dualFunctionOneAndTwo( -_ + _))
5.2.2 高阶函数 在 Scala 中,函数是一等公民。怎么体现的呢?
对于一个函数我们可以:定义函数、调用函数
函数作为值传递 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def f (n: Int ): Int = { println("f调用" ) n + 1 } val result: Int = f(123 ) println(result) val f1: Int =>Int = f val f2 = f _ println(f1) println(f1(12 )) println(f2) println(f2(35 )) f调用 124 chapter05.Test06_HighOrderFunction $$$Lambda $5 /2096171631 @6 debcae2 f调用 13 chapter05.Test06_HighOrderFunction $$$Lambda $6 /2114694065 @5 ba23b66 f调用 36
1 2 3 4 5 6 7 8 9 10 11 12 def fun (): Int = { println("fun调用" ) 1 } val f3: ()=>Int = funval f4 = fun _println(f3) println(f4) chapter05.Test06_HighOrderFunction $$$Lambda $7 /2101440631 @35 bbe5e8 chapter05.Test06_HighOrderFunction $$$Lambda $8 /2109957412 @2 c8d66b2
函数作为参数传递 1 2 3 4 5 6 7 8 9 10 11 def dualEval (op: (Int , Int )=>Int , a: Int , b: Int ): Int = { op(a, b) } def add (a: Int , b: Int ): Int = { a + b } println(dualEval(add, 12 , 35 )) println(dualEval((a, b) => a + b, 12 , 35 )) println(dualEval(_ + _, 12 , 35 ))
函数作为返回值 1 2 3 4 5 6 7 8 9 10 11 12 13 def f5 (): Int =>Unit = { def f6 (a: Int ): Unit = { println("f6调用 " + a) } f6 } println(f5()(25 ))
5.2.3 高阶函数案例1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 object Test07_Practice_CollectionOperation { def main (args: Array [String ]): Unit = { val arr: Array [Int ] = Array (12 , 45 , 75 , 98 ) def arrayOperation (array: Array [Int ], op: Int =>Int ): Array [Int ] = { for (elem <- array) yield op(elem) } def addOne (elem: Int ): Int = { elem + 1 } val newArray: Array [Int ] = arrayOperation(arr, addOne) println(newArray.mkString("," )) val newArray2 = arrayOperation(arr, _ * 2 ) println(newArray2.mkString(", " )) } }
练习 1: 定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为Boolean。
要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
1 2 3 4 5 6 7 8 9 val fun = (i: Int , s: String , c: Char ) => { if (i == 0 && s == "" && c == '0 ') false else true } println(fun(0 , "" , '0 ')) println(fun(0 , "" , '1 ')) println(fun(23 , "" , '0 ')) println(fun(0 , "hello" , '0 '))
练习 2: 定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。函数 f2 接收一个 Char 类型的参数,返回一个Boolean 的值。
要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 object Test08_Practice { def main (args: Array [String ]): Unit = { def func (i: Int ): String =>(Char =>Boolean ) = { def f1 (s: String ): Char =>Boolean = { def f2 (c: Char ): Boolean = { if (i == 0 && s == "" && c == '0 ') false else true } f2 } f1 } println(func(0 )("" )('0 ')) println(func(0 )("" )('1 ')) println(func(23 )("" )('0 ')) println(func(0 )("hello" )('0 ')) def func1 (i: Int ): String =>(Char =>Boolean ) = { s => c => if (i == 0 && s == "" && c == '0 ') false else true } println(func1(0 )("" )('0 ')) println(func1(0 )("" )('1 ')) println(func1(23 )("" )('0 ')) println(func1(0 )("hello" )('0 ')) def func2 (i: Int )(s: String )(c: Char ): Boolean = { if (i == 0 && s == "" && c == '0 ') false else true } println(func2(0 )("" )('0 ')) println(func2(0 )("" )('1 ')) println(func2(23 )("" )('0 ')) println(func2(0 )("hello" )('0 ')) } }
5.2.4 函数柯里化&闭包闭包:函数式编程的标配
1) 说明
2) 案例实操 (1)闭包
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 object Test09_ClosureAndCurrying { def main (args: Array [String ]): Unit = { def add (a: Int , b: Int ): Int = { a + b } def addByFour (b: Int ): Int = { 4 + b } def addByFive (b: Int ): Int = { 5 + b } def addByFour1 (): Int =>Int = { val a = 4 def addB (b: Int ): Int = { a + b } addB } def addByA (a: Int ): Int =>Int = { def addB (b: Int ): Int = { a + b } addB } println(addByA(35 )(24 )) val addByFour2 = addByA(4 ) val addByFive2 = addByA(5 ) println(addByFour2(13 )) println(addByFive2(25 )) def addByA1 (a: Int ): Int =>Int = { (b: Int ) => { a + b } } def addByA2 (a: Int ): Int =>Int = { b => a + b } def addByA3 (a: Int ): Int =>Int = a + _ val addByFour3 = addByA3(4 ) val addByFive3 = addByA3(5 ) println(addByFour3(13 )) println(addByFive3(25 )) }
(2)柯里化
1 2 3 4 5 6 7 8 def addCurrying (a: Int )(b: Int ): Int = { a + b } println(addCurrying(35 )(24 ))
5.2.5 递归 1) 说明 一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用
2) 案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 import scala.annotation.tailrecobject Test10_Recursion { def main (args: Array [String ]): Unit = { println(fact(5 )) println(tailFact(5 )) } def fact (n: Int ): Int = { if (n == 0 ) return 1 fact(n - 1 ) * n } def tailFact (n: Int ): Int = { @tailrec def loop (n: Int , currRes: Int ): Int = { if (n == 0 ) return currRes loop(n - 1 , currRes * n) } loop(n, 1 ) } }
5.2.6 控制抽象1) 值调用:把计算后的值传递过去 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def f0 (a: Int ): Unit = { println("a: " + a) println("a: " + a) } f0(23 ) def f1 (): Int = { println("f1调用" ) 12 } f0(f1()) a: 23 a: 23 f1调用 a: 12 a: 12
2) 名调用:把代码传递过去 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 def f1 (): Int = { println("f1调用" ) 12 } def f2 (a: =>Int ): Unit = { println("a: " + a) println("a: " + a) } f2(23 ) f2(f1()) f2({ println("这是一个代码块" ) 29 }) a: 23 a: 23 f1调用 a: 12 f1调用 a: 12 这是一个代码块 a: 29 这是一个代码块 a: 29
3) 案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 object Test12_MyWhile { def main (args: Array [String ]): Unit = { var n = 10 while (n >= 1 ){ println(n) n -= 1 } def myWhile (condition: =>Boolean ): (=>Unit )=>Unit = { def doLoop (op: =>Unit ): Unit = { if (condition){ op myWhile(condition)(op) } } doLoop _ } println("=================" ) n = 10 myWhile(n >= 1 ){ println(n) n -= 1 } def myWhile2 (condition: =>Boolean ): (=>Unit )=>Unit = { op => { if (condition){ op myWhile2(condition)(op) } } } println("=================" ) n = 10 myWhile2(n >= 1 ){ println(n) n -= 1 } def myWhile3 (condition: =>Boolean )(op: =>Unit ): Unit = { if (condition){ op myWhile3(condition)(op) } } println("=================" ) n = 10 myWhile3(n >= 1 ){ println(n) n -= 1 } } }
5.2.7 惰性加载 1) 说明 当函数返回值被声明为时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行种函数我们称之为惰性函数。
2) 案例实操 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 object Test13_Lazy { def main (args: Array [String ]): Unit = { lazy val result: Int = sum(13 , 47 ) println("1. 函数调用" ) println("2. result = " + result) println("4. result = " + result) } def sum (a: Int , b: Int ): Int = { println("3. sum调用" ) a + b } } 1. 函数调用3. sum调用2. result = 60 4. result = 60