函数式编程
Scala语言是一个完全面向对象的编程语言。
面向对象:解决问题时,分解对象、行为、属性,然后通过对象的关系以及行为间的调用来解决问题。
函数式编程:解决问题时,将问题分解成一个一个的步骤,将每个步骤进行封装(函数),通过调用这些封装好的步骤解决问题。
函数基本语法
函数和方法:
- 为完成某一功能的程序语句的集合称为函数
- 类中的函数叫做方法
函数示例:
def sum(x: Int, y: Int): Int = {x + y}

任意地方都可以定义函数,例如在main方法中
def main(args: Array[String]): Unit = {// 定义函数def hello(name: String): Unit = {println("hello, " + name)}// 调用函数hello("张三")}
函数参数
可变参数
使用*表示可变参数。
如果参数列表中存在多个参数,可变参数一般放在最后。
def fun1(str: String*): Unit={ // 使用 String* 接收多个字符串参数println(str)}fun1("aaa", "bbb", "ccc")
参数默认值
定义函数时可以定义默认值:
def fun2(name: String, age:Int=18):Unit = {println(s"姓名:${name}, 年龄:${age}")}fun2("张三")fun2("李四", 22)
带名参数
参数赋值时,可以打乱参数顺序,通过参数名称进行赋值:
一般多用在有默认值的参数列表情况下。
def fun3(name: String = "小红", age:Int):Unit = {println(s"姓名:${name}, 年龄:${age}")}fun3(age = 22, name = "王五") // 通过参数名称进行赋值fun3(age = 22) // name已存在默认值,只需传递age
函数类型的参数
函数的参数也可以是函数:
// 接收一个函数式参数def fun11(func: String => Unit): Unit = {func("张三")}fun11(name => println(name)) // 调用fun11,传入一个lambda表达式
函数至简原则
普通函数至简原则
// 完整版def fun4(name: String): String = {return name}
至简原则:
return可以省略,Scala会使用函数体的最后一行代码作为返回值def fun4(name: String): String = {name}
如果函数只有一行代码,可以省略花括号
def fun4(name: String): String = name
返回值类型如果能够清晰推断出来,那么可以省略
def fun4(name: String)= name
如果有
return,则不能省略返回值类型,必须指定- 如果函数明确声明
Unit(无返回值),那么及时函数体中使用return关键字也不起作用 Scala如果期望是无返回值类型,那么可以省略等号,简称“过程”
def fun4(name: String) {name}
如果函数无参,但是声明了参数列表,那么调用时的小括号可以省略(也可以加上) ```scala def fun8() { println(“hello”) }
// 没有参数的函数调用时,可以省略小括号 fun8() fun8
- 如果函数没有参数列表,那么小括号可以省略,调用时小括号必须省略```scala// 如果函数没有参数,则定义时也可以省略小括号def fun9 {println("hello")}// 如果定义时省略了小括号,则调用时也必须不写小括号fun9
- 如果不关心名称,只关心逻辑处理,那么函数名(
def)可以省略,匿名函数 ```scala def fun10(name: String): Unit = { println(name) }
// 简写成lambda表达式 (name: String) => { println(name) }
<a name="39f1f957"></a>## 匿名函数至简原则传入的匿名lambda至简原则:- 参数的类型可以省略,会根据形参进行自动推导- 类型省略之后,如果只有一个参数,则小括号可以省略(没有参数或者有多个参数时不能省略括号)- 匿名函数如果只有一行,可以省略大括号- 如果参数只出现一次,则参数省略且后面参数可以用`_`代替- 如果可以推断出当前传入的是一个函数体,而不是调用语句,则连`_`也可以省略示例:```scala// 接收一个函数式参数def fun11(func: String => Unit): Unit = {func("张三")}// 完整版lambda表达式传入fun11((name: String) => { println(name) })// 简写后:fun11(name => println(name))// 进一步简化fun11(println(_))// 进一步简化fun11(println)
def fun12(func:(Int, Int) => Int): Int = {func(1, 2)}// 完整写法println(fun12((a: Int, b:Int) => a + b))// a、b都只出现一次,可以使用下换线代替。第1个下划线是第1个参数,第2个下划线是第2个参数println(fun12(_ + _))println(fun12((a: Int, b:Int) => a - b))println(fun12(_ - _))println(fun12((a: Int, b:Int) => b - a))println(fun12(-_ + _))
高阶函数
函数作为值传递
函数可以作为值进行传递(不调用函数,传递函数本身):
def fun13(n:Int) : Int = {println("fun13调用了")n + 1}val fun13Result: Int = fun13(1) // 调用函数,得到函数运行的返回值// 语法:函数名 _val intToInt: Int => Int = fun13 _ // 得到的是函数,而不是调用函数val fun13Function2 = fun13 _ // 简写val fun13Function3: Int => Int = fun13 // 当变量的类型声明为一个函数类型时,后面的函数下划线可以省略println(fun13Function1) // 打印的是一个函数对象println(fun13Function1(123)) // 可以正常调用
函数作为参数传递
示例:
def fun14(op: (Int, Int) => Int, a: Int, b: Int): Int = {op(a, b)}def fun15(a: Int, b: Int): Int = {a + b}println(fun14(fun15, 1, 2)) // 将fun15函数作为参数传递给fun14println(fun14((a, b) => a + b, 1, 2)) // 将匿名函数(lambda表达式)传递给fun14println(fun14(_ + _, 1, 2)) // 将简化lambda作为参数传递给fun14
函数作为返回值返回
示例:
def fun16(): (Int, Int) => Int = {def fun17(a: Int, b: Int): Int = {a + b}fun17 _ // 将fun17函数作为返回值返回。因为此处函数返回值类型已经声明是一个函数,// 所以此处的下划线可以省略,直接写成fun17}val fun16Result = fun16() // 此处得到的结果是 fun17这个函数println(fun16()(1,2)) // 调用fun16,然后接着调用
函数作为返回值返回的嵌套:
def fun18(i: Int): String=>(Char=>Boolean) = {def fun19(s: String): Char=>Boolean = {def fun20(c: Char): Boolean = {if (i ==0 && s == "" && c == '0') false else true}fun20 _}fun19 _}// fun18可以简写为:def fun21(i: Int): String=>(Char=>Boolean) = {s => c => if (i == 0 && s == "" && c == '0') false else true}// 最后可以实现类似分步调用的效果println(fun21(0)("")('0'))println(fun21(1)("")('0'))
对于上面的例子,可以进一步简写为:
// 函数柯里化def fun22(i: Int)(s: String)(c: Char): Boolean = {if (i ==0 && s == "" && c == '0') false else true}println(fun22(0)("")('0'))println(fun22(1)("")('0'))
函数柯里化和闭包
闭包:如果一个函数访问到了它的外部(局部)变量的值,那么这个函数和它所处的环境称为闭包。
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
闭包是函数式编程的标配。
闭包示例:
// 不使用闭包时,可能要定义一个多参的函数def fun1(a: Int, b: Int): Int = {a + b // 但实际上,我们的a只有固定的几个值,将其作为变量和实际不符,会影响效率}// 或者罗列出a的值,定义多个函数def fun2(b: Int): Int = {3 + b}def fun4(b: Int): Int = {4 + b // 当a的值比较多时,需要罗列很多函数}// 使用闭包定义函数def fun23(a: Int): Int=>Int = {def fun24(b: Int): Int = {// fun24访问的a是fun23的变量,而不是一个固定的值。// 这样组成一个闭包,就可以先通过fun23的参数得到a变量,然后再传给fun24执行,避免了要编写多个a不同值的fun24。// 而且因为使用了闭包,所以fun24可以正常拿到a的值a + b}fun24 _}println(fun23(3)(4))// 简写def fun25(a: Int): Int=>Int = a + _println(fun25(4)(5))
以柯里化形式编写:
def fun26(a: Int)(b: Int): Int = {a + b}
递归
递归:
- 方法调用自身
- 方法必须要有跳出的逻辑
- 方法调用自身时,传递的参数应该有规律
- Scala中的递归必须声明函数返回值类型
示例:
// 阶乘def fun27(n: Int): Int = {if(n == 0) return 1fun27(n - 1) * n}println(fun27(5))
以尾递归方式实现:
尾递归:方法最后执行递归时,不再是一层层嵌套。是递归调用自身时,已经执行了的方法栈帧可以直接销毁,因为递归的代码正好就是最后一句,且不需要再通过栈帧获取上一层执行时的值。使用尾递归可以防止栈溢出。
def fun28(n: Int): Int = {@tailrec // tailrec注解:校验当前方法是否是尾递归,如果不是,则会报错。// 在Idea编译器中,尾递归和普通递归的方法前面的标志符号也不同def fun29(n: Int, currRes: Int): Int = {if(n == 0) return currResfun29(n - 1, currRes * n)}fun29(n, 1)}println(fun28(5))
控制抽象
值调用:把计算后的值传递过去
名调用:把代码传递过去
值调用示例:
def fun29(a: Int): Unit = {println(s"a: ${a}")}def fun30(): Int = {println("fun30调用了")11}fun29(fun30()) // 执行fun30(),将fun30计算的返回值当做fun29的参数
名调用示例:
def fun30(): Int = {println("fun30调用了")11}// 传入的a为一个Int返回值的代码块// 代码块的类型是 =>Int ,而无参匿名参数的类型是 ()=>Int,两个不一样def fun31(a: =>Int): Unit = {println(s"a: ${a}")println(s"a: ${a}")}fun31(11) // 直接传入Int数值也可以fun31(fun30()) // 将fun30函数的代码块作为参数传递给fun31// 传入一个代码块fun31({println("这是一个代码块")22})
传入代码块和传入匿名无参函数的区别:
// 传入代码块def fun31(a: =>Int): Unit = {println(s"a: ${a}")println(s"a: ${a}")}fun31(fun30()) // 传入的是fun30函数的代码块// 传入匿名无参函数def fun32(a: ()=>Int): Unit = {println(s"a: ${a}")println(s"a: ${a}")}fun32(fun30) // 传入的是fun30函数本身,不能带有小括号调用
自定义While循环
使用名传递(代码块传参)、递归编写自定义的while循环函数:
// 此处传入的condition不是一个计算的值,而是一个表达式,每次都要进行计算。// 否则如果第一个计算为true,就会一直为truedef fun34(condition: =>Boolean): (=>Unit)=>Unit = {// 执行的操作op也是一个代码块def fun35(op: => Unit): Unit = {if(condition) {opfun34(condition)(op) // 递归调用}}fun35 _}var n:Int = 10fun34(n >= 1) {println(n)n -= 1}
使用柯里化进行简写:
def fun36(condition: =>Boolean)(op: =>Unit): Unit = {if(condition) {opfun36(condition)(op)}}n = 10fun36(n >= 1) {println(n)n -= 1}
惰性加载
当函数返回值被声明为lazy时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数称为惰性函数。
示例:
def fun37(a: Int): Int = {println("fun37调用了")a + 1}println("==========================")lazy val fun37Result:Int = fun37(11) // 使用lazy关键字后,fun37方法并不会立即执行println("fun37Result=====================")println("fun37: " + fun37Result) // 此处用到了fun37Result,此处才会真正进行fun37方法的调用执行println("fun37-2: " + fun37Result) // 因为fun37方法已经进行了调用执行,所以fun37Result已经成了具体的值,// 不是传名参数代码块,所以此处也不再调用fun37方法
