函数高级
高阶函数
在 Scala 中,函数是一等公民,对于一个函数我们可以:定义函数、调用函数
高阶运用:
1、函数可以作为值进行传递
object TestFunction {
def main(args: Array[String]): Unit = {
def f(n: Int): Int = {
println("f被调用啦")
n + 1
}
val result = f(123)
println(result) // 124
val f1: Int => Int = f
val f2 = f _
println(f1) // 函数的引用地址
println(f1(100)) // 101
println(f2(200)) // 201
def fun(): Int = {
println("fun被调用啦")
1
}
val f3 = fun _
val f4: () => Int = fun
}
}
2、函数可以作为参数进行传递
object TestFunction {
def main(args: Array[String]): Unit = {
// 定义二元计算函数
def dualEval(op: (Int, Int) => Int, a: Int, b: Int): Int = {
op(a, b)
}
def add (a: Int, b: Int) : Int = {
a + b
}
dualEval(add, 12, 15)
dualEval((a, b) => a + b, 12, 15)
}
}
3、函数可以作为函数返回值返回
object TestFunction {
def main(args: Array[String]): Unit = {
def f5(): Int => Unit = {
def f6 (a: Int): Unit = {
println("f6备调用啦" + a)
}
f6 // 将函数直接返回
}
val f6 = f5()
println(f6) // 得到的是f6的引用
println(f6(123)) // f6备调用啦123
println(f5()(25)) // f6备调用啦25
}
}
实例
object TestPractice {
def main(args: Array[String]): Unit = {
// 定义一个匿名函数,并将它作为值赋给变量 fun。函数有三个参数,类型分别为 Int,String,Char,返回值类型为 Boolean。
// 要求调用函数 fun(0, “”, ‘0’)得到返回值为 false,其它情况均返回 true。
val fun = (i: Int, s: String, c: Char) => { if (i == 0 && s == "" && c == '0') false else true }
println(fun(0, "", '0')) // false
println(fun(1, "", '0')) // true
println(fun(0, "1", '0')) // true
println(fun(0, "", '1')) // true
println(fun(1, "1", '1')) // true
// 定义一个函数 func,它接收一个 Int 类型的参数,返回一个函数(记作 f1)。
// 它返回的函数 f1,接收一个 String 类型的参数,同样返回一个函数(记作 f2)。
// 函数 f2 接收一个 Char 类型的参数,返回一个 Boolean 的值。
// 要求调用函数 func(0) (“”) (‘0’)得到返回值为 false,其它情况均返回 true。
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')) // false
println(func(1)("")('0')) // true
println(func(0)("1")('0')) // true
println(func(0)("")('1')) // true
println(func(1)("1")('1')) // true
// 匿名函数的简写
def func1(i: Int): String => (Char => Boolean) = {
s => c => if (i == 0 && s == "" && c == '0') false else true
}
// 柯里化
def func2(i: Int)(s: String)(c: Char): Boolean = {
if (i == 0 && s == "" && c == '0') false else true
}
}
}
匿名函数
没有名字的函数就是匿名函数。
(x: Int) => {函数体}
x:表示输入参数类型;Int:表示输入参数类型;函数体:表示具体代码逻辑
传递匿名函数至简原则:
(1)参数的类型可以省略,会根据形参进行自动的推导
(2)类型省略之后,发现只有一个参数,则圆括号可以省略;其他情况:没有参数和参数超过 1 的永远不能省略圆括号。
(3)匿名函数如果只有一行,则大括号也可以省略
(4)如果参数只出现一次,则参数省略且后面参数可以用_代替
object TestLambda {
def main(args: Array[String]): Unit = {
val fun = (name: String) => { println(name) }
fun("test") // test
// 定义一个函数,以函数作为参数输入
def f(func: String => Unit): Unit = {
func("testF")
}
f(fun) // testF
f((name: String) => { println(s"---$name") }) // ---testF
// (1)参数的类型可以省略,会根据形参进行自动的推导
f((name) => { println(s"---$name") })
// (2)类型省略之后,发现只有一个参数,则圆括号可以省略; 其他情况:没有参数和参数超过1的永远不能省略圆括号。
f(name => { println(s"---$name") })
// (3)匿名函数如果只有一行,则大括号也可以省略
f(name => println(s"---$name"))
// (4)如果参数只出现一次,则参数省略且后面参数可以用_代替
f(println(_))
// (5)如果可以推断出,当前传入的println是一个函数体,而不是调用语句,可以直接省略下划线
f(println)
// 实例,定义一个二元运算函数,只操作1和2,但是具体的运算通过参数传入
def dualFunctionOneAndTwo(fun: (Int, Int) => Int): Unit = {
fun(1, 2)
}
val add = (a: Int, b:Int) => a + b
val minus = (a: Int, b:Int) => a - b
dualFunctionOneAndTwo(add)
dualFunctionOneAndTwo(minus)
// 简化:
dualFunctionOneAndTwo((a: Int, b: Int) => a + b)
dualFunctionOneAndTwo((a, b) => a + b)
dualFunctionOneAndTwo(_ + _)
}
}
高阶函数案例
Map映射
object TestPracticeCollection {
def main(args: Array[String]): Unit = {
val arr: Array[Int] = Array(1, 11, 111, 1111)
// 对数组进行处理,讲操作抽象出来,处理完毕的结果返回一个新的数组
def arrayOperation(array: Array[Int], op: Int => Int): Array[Int] = {
for (elem <- array) yield op(elem)
}
// 定义一个加1操作
def addOne(a : Int): Int = {
a + 1
}
// 调用函数
val newArray = arrayOperation(arr, addOne)
println(newArray.mkString(",")) // 2,12,112,1112
// 调用匿名函数
val newArray2 = arrayOperation(arr, (a) => a * 2)
println(newArray2.mkString(",")) // 2,22,222,2222
}
}
函数柯里化&闭包
闭包:函数式编程的标配
闭包:如果一个函数,访问到了它的外部(局部)变量的值,那么这个函数和他所处的环境,称为闭包
函数柯里化:把一个参数列表的多个参数,变成多个参数列表。
object TestClosureAndCurrying {
def main(args: Array[String]): Unit = {
def add(a: Int, b: Int): Int = {
a + b
}
// 1.考虑固定一个加数的场景
def addByFour(b: Int): Int = {
4 + b
}
// 2.拓展固定加数改变的情况
def addFive(b: Int): Int = {
5 + b
}
// 3.将固定加数作为另一个参数传入,但是是作为“第一层参数”传入
def addByFour1(): Int => Int = {
def addB(b: Int): Int = {
4 + b
}
addB
}
def addByA(a: Int): Int => Int = {
def addB(b: Int): Int = {
a + b
}
addB
}
val addByFour2 = addByA(4)
val addByFive2 = addByA(5)
println(addByFour2(15)) // 19
println(addByFive2(10)) // 15
// 4.addByA 的Lambda表达式
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 = addByA(4)
val addByFive3 = addByA(5)
println(addByFour3(15)) // 19
println(addByFive3(10)) // 15
// 5.柯里化
def addByCurrying(a: Int)(b: Int):Int = { a + b }
println(addByCurrying(12)(13)) // 25
}
}
递归
一个函数/方法在函数/方法体内又调用了本身,我们称之为递归调用,这样的函数就叫做递归函数
递归算法
-
方法调用自身
-
方法必须要有跳出的逻辑
-
方法调用自身时,传递的参数应该有规律
-
scala 中的递归必须声明函数返回值类型
object TestRecursion {
def main(args: Array[String]): Unit = {
println(fact(5)) // 120
}
// 递归实现阶乘
def fact(i: Int): Int = {
if (i == 0) {
// 0! = 1
// 只有最后一行代码的return可以省略
return 1
}
fact(i - 1) * i
}
// 尾递归实现
def tailFact(n: Int): Int = {
// 这个注解当方法不是尾递归方法时会报错
@tailrec
def loop(n: Int, result: Int): Int = {
if (n == 0) return result
loop(n - 1, result * n)
}
loop(n, 1)
}
}
递归会耗费更多的栈空间资源,有可能会导致栈溢出。尾递归的话,每次递归会将上次递归调用弹出,并将本次调用压入。
控制抽象
1、值调用(传值参数):把计算后的值传递过去
2、名调用(传名参数):把代码传递过去
Java 只有值调用;Scala 既有值调用,又有名调用
object TestControlAbstraction {
def main(args: Array[String]): Unit = {
// 1.传值参数
def f0 (a: Int): Unit = {
println("a: " + a)
}
f0(123) // a: 123
def f1(): Int = {
println("f1被调用")
12
}
f0(f1()) // a: 12
// 2.传名参数,参数不再是具体的值,而是代码块,这里的代码块的返回值是Int
def f2 (b: => Int): Unit = {
println("b: " + b)
println("b: " + b)
}
f2(23)
// f1将会被调用两次,因为这里我们把b就直接当前成了f1()来使用了,因此名传递是将代码块直接传递进去
// f1被调用
// b: 12
// f1被调用
// b: 12
f2(f1())
// 代码块会被调用两次,并且返回值是129
// 这里是一个代码块
// b: 129
// 这里是一个代码块
// b: 129
f2({
println("这里是一个代码块")
129
})
}
}
自定义一个while循环
object TestMyWhile {
def main(args: Array[String]): Unit = {
var n = 10
// 1.常规的while循环
while (n >= 1) {
println(n)
n -= 1
}
// 2.用闭包实现一个函数,将代码块作为参数传入,递归调用
def myWhile(condition: => Boolean): (=> Unit) => Unit = {
// 内层函数需要递归调用,参数就是循环体
def doLoop(op: => Unit): Unit = {
if (condition) {
op
myWhile(condition)(op)
}
}
doLoop _
}
n = 10
myWhile(n >= 1) {
println(n)
n -= 1
}
// 3.用匿名函数实现
def myWhile2(condition: => Boolean): (=> Unit) => Unit = {
// 内层函数需要递归调用,参数就是循环体
op => {
if (condition) {
op
myWhile2(condition)(op)
}
}
}
n = 10
myWhile2(n >= 1) {
println(n)
n -= 1
}
// 4,用柯里化实现
def myWhile3(condition: => Boolean)(op: => Unit):Unit = {
// 内层函数需要递归调用,参数就是循环体
if (condition) {
op
myWhile3(condition)(op)
}
}
n = 10
myWhile3(n >= 1) {
println(n)
n -= 1
}
}
}
惰性加载
当函数返回值被声明为 lazy 时,函数的执行将被推迟,直到我们首次对此取值,该函数才会执行。这种函数我们称之为惰性函数。这种加载方式称之为惰性加载。
lazy 不能修饰 var 类型的变量
object TestLazy {
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