函数式编程
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函数作为参数传递给fun14
println(fun14((a, b) => a + b, 1, 2)) // 将匿名函数(lambda表达式)传递给fun14
println(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 1
fun27(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 currRes
fun29(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,就会一直为true
def fun34(condition: =>Boolean): (=>Unit)=>Unit = {
// 执行的操作op也是一个代码块
def fun35(op: => Unit): Unit = {
if(condition) {
op
fun34(condition)(op) // 递归调用
}
}
fun35 _
}
var n:Int = 10
fun34(n >= 1) {
println(n)
n -= 1
}
使用柯里化进行简写:
def fun36(condition: =>Boolean)(op: =>Unit): Unit = {
if(condition) {
op
fun36(condition)(op)
}
}
n = 10
fun36(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方法