函数式编程

Scala语言是一个完全面向对象的编程语言。

面向对象:解决问题时,分解对象、行为、属性,然后通过对象的关系以及行为间的调用来解决问题。

函数式编程:解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤解决问题。

函数基本语法

函数和方法:

  • 为完成某一功能的程序语句的集合称为函数
  • 类中的函数叫做方法

函数示例:

  1. def sum(x: Int, y: Int): Int = {
  2. x + y
  3. }

image.png
任意地方都可以定义函数,例如在main方法中

  1. def main(args: Array[String]): Unit = {
  2. // 定义函数
  3. def hello(name: String): Unit = {
  4. println("hello, " + name)
  5. }
  6. // 调用函数
  7. hello("张三")
  8. }

函数参数

可变参数

使用*表示可变参数。
如果参数列表中存在多个参数,可变参数一般放在最后。

  1. def fun1(str: String*): Unit={ // 使用 String* 接收多个字符串参数
  2. println(str)
  3. }
  4. fun1("aaa", "bbb", "ccc")

参数默认值

定义函数时可以定义默认值:

  1. def fun2(name: String, age:Int=18):Unit = {
  2. println(s"姓名:${name}, 年龄:${age}")
  3. }
  4. fun2("张三")
  5. fun2("李四", 22)

带名参数

参数赋值时,可以打乱参数顺序,通过参数名称进行赋值:
一般多用在有默认值的参数列表情况下。

  1. def fun3(name: String = "小红", age:Int):Unit = {
  2. println(s"姓名:${name}, 年龄:${age}")
  3. }
  4. fun3(age = 22, name = "王五") // 通过参数名称进行赋值
  5. fun3(age = 22) // name已存在默认值,只需传递age

函数类型的参数

函数的参数也可以是函数:

  1. // 接收一个函数式参数
  2. def fun11(func: String => Unit): Unit = {
  3. func("张三")
  4. }
  5. fun11(name => println(name)) // 调用fun11,传入一个lambda表达式

函数至简原则

普通函数至简原则

  1. // 完整版
  2. def fun4(name: String): String = {
  3. return name
  4. }

至简原则:

  • return可以省略,Scala会使用函数体的最后一行代码作为返回值

    1. def fun4(name: String): String = {
    2. name
    3. }
  • 如果函数只有一行代码,可以省略花括号

    1. def fun4(name: String): String = name
  • 返回值类型如果能够清晰推断出来,那么可以省略

    1. def fun4(name: String)= name
  • 如果有return,则不能省略返回值类型,必须指定

  • 如果函数明确声明Unit(无返回值),那么及时函数体中使用return关键字也不起作用
  • Scala如果期望是无返回值类型,那么可以省略等号,简称“过程”

    1. def fun4(name: String) {
    2. name
    3. }
  • 如果函数无参,但是声明了参数列表,那么调用时的小括号可以省略(也可以加上) ```scala def fun8() { println(“hello”) }

// 没有参数的函数调用时,可以省略小括号 fun8() fun8

  1. - 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略
  2. ```scala
  3. // 如果函数没有参数,则定义时也可以省略小括号
  4. def fun9 {
  5. println("hello")
  6. }
  7. // 如果定义时省略了小括号,则调用时也必须不写小括号
  8. fun9
  • 如果不关心名称,只关心逻辑处理,那么函数名(def)可以省略,匿名函数 ```scala def fun10(name: String): Unit = { println(name) }

// 简写成lambda表达式 (name: String) => { println(name) }

  1. <a name="39f1f957"></a>
  2. ## 匿名函数至简原则
  3. 传入的匿名lambda至简原则:
  4. - 参数的类型可以省略,会根据形参进行自动推导
  5. - 类型省略之后,如果只有一个参数,则小括号可以省略(没有参数或者有多个参数时不能省略括号)
  6. - 匿名函数如果只有一行,可以省略大括号
  7. - 如果参数只出现一次,则参数省略且后面参数可以用`_`代替
  8. - 如果可以推断出当前传入的是一个函数体,而不是调用语句,则连`_`也可以省略
  9. 示例:
  10. ```scala
  11. // 接收一个函数式参数
  12. def fun11(func: String => Unit): Unit = {
  13. func("张三")
  14. }
  15. // 完整版lambda表达式传入
  16. fun11((name: String) => { println(name) })
  17. // 简写后:
  18. fun11(name => println(name))
  19. // 进一步简化
  20. fun11(println(_))
  21. // 进一步简化
  22. fun11(println)
  1. def fun12(func:(Int, Int) => Int): Int = {
  2. func(1, 2)
  3. }
  4. // 完整写法
  5. println(fun12((a: Int, b:Int) => a + b))
  6. // a、b都只出现一次,可以使用下换线代替。第1个下划线是第1个参数,第2个下划线是第2个参数
  7. println(fun12(_ + _))
  8. println(fun12((a: Int, b:Int) => a - b))
  9. println(fun12(_ - _))
  10. println(fun12((a: Int, b:Int) => b - a))
  11. println(fun12(-_ + _))

高阶函数

函数作为值传递

函数可以作为值进行传递(不调用函数,传递函数本身):

  1. def fun13(n:Int) : Int = {
  2. println("fun13调用了")
  3. n + 1
  4. }
  5. val fun13Result: Int = fun13(1) // 调用函数,得到函数运行的返回值
  6. // 语法:函数名 _
  7. val intToInt: Int => Int = fun13 _ // 得到的是函数,而不是调用函数
  8. val fun13Function2 = fun13 _ // 简写
  9. val fun13Function3: Int => Int = fun13 // 当变量的类型声明为一个函数类型时,后面的函数下划线可以省略
  10. println(fun13Function1) // 打印的是一个函数对象
  11. println(fun13Function1(123)) // 可以正常调用

函数作为参数传递

示例:

  1. def fun14(op: (Int, Int) => Int, a: Int, b: Int): Int = {
  2. op(a, b)
  3. }
  4. def fun15(a: Int, b: Int): Int = {
  5. a + b
  6. }
  7. println(fun14(fun15, 1, 2)) // 将fun15函数作为参数传递给fun14
  8. println(fun14((a, b) => a + b, 1, 2)) // 将匿名函数(lambda表达式)传递给fun14
  9. println(fun14(_ + _, 1, 2)) // 将简化lambda作为参数传递给fun14

函数作为返回值返回

示例:

  1. def fun16(): (Int, Int) => Int = {
  2. def fun17(a: Int, b: Int): Int = {
  3. a + b
  4. }
  5. fun17 _ // 将fun17函数作为返回值返回。因为此处函数返回值类型已经声明是一个函数,
  6. // 所以此处的下划线可以省略,直接写成fun17
  7. }
  8. val fun16Result = fun16() // 此处得到的结果是 fun17这个函数
  9. println(fun16()(1,2)) // 调用fun16,然后接着调用

函数作为返回值返回的嵌套:

  1. def fun18(i: Int): String=>(Char=>Boolean) = {
  2. def fun19(s: String): Char=>Boolean = {
  3. def fun20(c: Char): Boolean = {
  4. if (i ==0 && s == "" && c == '0') false else true
  5. }
  6. fun20 _
  7. }
  8. fun19 _
  9. }
  10. // fun18可以简写为:
  11. def fun21(i: Int): String=>(Char=>Boolean) = {
  12. s => c => if (i == 0 && s == "" && c == '0') false else true
  13. }
  14. // 最后可以实现类似分步调用的效果
  15. println(fun21(0)("")('0'))
  16. println(fun21(1)("")('0'))

对于上面的例子,可以进一步简写为:

  1. // 函数柯里化
  2. def fun22(i: Int)(s: String)(c: Char): Boolean = {
  3. if (i ==0 && s == "" && c == '0') false else true
  4. }
  5. println(fun22(0)("")('0'))
  6. println(fun22(1)("")('0'))

函数柯里化和闭包

闭包:如果一个函数访问到了它的外部(局部)变量的值,那么这个函数和它所处的环境称为闭包。

函数柯里化:把一个参数列表的多个参数,变成多个参数列表。

闭包是函数式编程的标配。

闭包示例:

  1. // 不使用闭包时,可能要定义一个多参的函数
  2. def fun1(a: Int, b: Int): Int = {
  3. a + b // 但实际上,我们的a只有固定的几个值,将其作为变量和实际不符,会影响效率
  4. }
  5. // 或者罗列出a的值,定义多个函数
  6. def fun2(b: Int): Int = {
  7. 3 + b
  8. }
  9. def fun4(b: Int): Int = {
  10. 4 + b // 当a的值比较多时,需要罗列很多函数
  11. }
  12. // 使用闭包定义函数
  13. def fun23(a: Int): Int=>Int = {
  14. def fun24(b: Int): Int = {
  15. // fun24访问的a是fun23的变量,而不是一个固定的值。
  16. // 这样组成一个闭包,就可以先通过fun23的参数得到a变量,然后再传给fun24执行,避免了要编写多个a不同值的fun24。
  17. // 而且因为使用了闭包,所以fun24可以正常拿到a的值
  18. a + b
  19. }
  20. fun24 _
  21. }
  22. println(fun23(3)(4))
  23. // 简写
  24. def fun25(a: Int): Int=>Int = a + _
  25. println(fun25(4)(5))

以柯里化形式编写:

  1. def fun26(a: Int)(b: Int): Int = {
  2. a + b
  3. }

递归

递归:

  • 方法调用自身
  • 方法必须要有跳出的逻辑
  • 方法调用自身时,传递的参数应该有规律
  • Scala中的递归必须声明函数返回值类型

示例:

  1. // 阶乘
  2. def fun27(n: Int): Int = {
  3. if(n == 0) return 1
  4. fun27(n - 1) * n
  5. }
  6. println(fun27(5))

以尾递归方式实现:

尾递归:方法最后执行递归时,不再是一层层嵌套。是递归调用自身时,已经执行了的方法栈帧可以直接销毁,因为递归的代码正好就是最后一句,且不需要再通过栈帧获取上一层执行时的值。使用尾递归可以防止栈溢出。

  1. def fun28(n: Int): Int = {
  2. @tailrec // tailrec注解:校验当前方法是否是尾递归,如果不是,则会报错。
  3. // 在Idea编译器中,尾递归和普通递归的方法前面的标志符号也不同
  4. def fun29(n: Int, currRes: Int): Int = {
  5. if(n == 0) return currRes
  6. fun29(n - 1, currRes * n)
  7. }
  8. fun29(n, 1)
  9. }
  10. println(fun28(5))

控制抽象

值调用:把计算后的值传递过去

名调用:把代码传递过去

值调用示例:

  1. def fun29(a: Int): Unit = {
  2. println(s"a: ${a}")
  3. }
  4. def fun30(): Int = {
  5. println("fun30调用了")
  6. 11
  7. }
  8. fun29(fun30()) // 执行fun30(),将fun30计算的返回值当做fun29的参数

名调用示例:

  1. def fun30(): Int = {
  2. println("fun30调用了")
  3. 11
  4. }
  5. // 传入的a为一个Int返回值的代码块
  6. // 代码块的类型是 =>Int ,而无参匿名参数的类型是 ()=>Int,两个不一样
  7. def fun31(a: =>Int): Unit = {
  8. println(s"a: ${a}")
  9. println(s"a: ${a}")
  10. }
  11. fun31(11) // 直接传入Int数值也可以
  12. fun31(fun30()) // 将fun30函数的代码块作为参数传递给fun31
  13. // 传入一个代码块
  14. fun31({
  15. println("这是一个代码块")
  16. 22
  17. })

传入代码块和传入匿名无参函数的区别:

  1. // 传入代码块
  2. def fun31(a: =>Int): Unit = {
  3. println(s"a: ${a}")
  4. println(s"a: ${a}")
  5. }
  6. fun31(fun30()) // 传入的是fun30函数的代码块
  7. // 传入匿名无参函数
  8. def fun32(a: ()=>Int): Unit = {
  9. println(s"a: ${a}")
  10. println(s"a: ${a}")
  11. }
  12. fun32(fun30) // 传入的是fun30函数本身,不能带有小括号调用

自定义While循环

使用名传递(代码块传参)、递归编写自定义的while循环函数:

  1. // 此处传入的condition不是一个计算的值,而是一个表达式,每次都要进行计算。
  2. // 否则如果第一个计算为true,就会一直为true
  3. def fun34(condition: =>Boolean): (=>Unit)=>Unit = {
  4. // 执行的操作op也是一个代码块
  5. def fun35(op: => Unit): Unit = {
  6. if(condition) {
  7. op
  8. fun34(condition)(op) // 递归调用
  9. }
  10. }
  11. fun35 _
  12. }
  13. var n:Int = 10
  14. fun34(n >= 1) {
  15. println(n)
  16. n -= 1
  17. }

使用柯里化进行简写:

  1. def fun36(condition: =>Boolean)(op: =>Unit): Unit = {
  2. if(condition) {
  3. op
  4. fun36(condition)(op)
  5. }
  6. }
  7. n = 10
  8. fun36(n >= 1) {
  9. println(n)
  10. n -= 1
  11. }

惰性加载

当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数称为惰性函数。

示例:

  1. def fun37(a: Int): Int = {
  2. println("fun37调用了")
  3. a + 1
  4. }
  5. println("==========================")
  6. lazy val fun37Result:Int = fun37(11) // 使用lazy关键字后,fun37方法并不会立即执行
  7. println("fun37Result=====================")
  8. println("fun37: " + fun37Result) // 此处用到了fun37Result,此处才会真正进行fun37方法的调用执行
  9. println("fun37-2: " + fun37Result) // 因为fun37方法已经进行了调用执行,所以fun37Result已经成了具体的值,
  10. // 不是传名参数代码块,所以此处也不再调用fun37方法