- Scala混合了
面向对象和函数式
的特性,在函数式编程语言中,函数是头等公民- 我们通常将可以作为参数传递到方法中的表达式叫做函数
- 将能够接收函数作为参数或者返回函数的函数,叫做高阶函数
- 高阶函数包含:作为值的函数、作为参数的函数、匿名函数、闭包、柯里化等。
函数作为值、作为参数、匿名函数-★★★★
- 完整语法:
- val函数名称 :(参数类型)=>函数返回值类型 = (参数名称:参数类型)=>函数体
- 简写语法:
- val函数名称 = (参数名称:参数类型) => 函数体
- 符号解释
- = 表示将右边的函数赋给左边的变量
- => 左面表示输入参数名称和类型,右边表示函数的实现和返回值类型
- 匿名函数
- 没有将函数赋给变量的函数叫做匿名函数。
- 方法转函数
- val 函数名 = 方法名 _
package cn.hanjiaxiaozhi.basic3
/**
* Author hanjiaxiaozhi
* Date 2020/7/13 16:24
* Desc 演示证明Scala中的函数的本质是对象
* 1.完整语法
* val 函数名 :(参数类型)=>返回值类型 = (参数名称:参数类型)=>{函数体}
* 2.简写语法
* val 函数名 = (参数名称:参数类型)=>{函数体}
*/
object FunctionDemo2_Scala {
def main(args: Array[String]): Unit = {
//根据我们之前学习的编程语言,如果一个东西是对象的话吗,那么应该有如下的特征:
//1.可以调用方法
//2.可以作为值赋值给变量接收
//3.可以当作参数被传递给方法
//那么如果能演示出上面的三点不就证明了函数是对象嘛!!!
//定义一些函数
val f1 = (x: Int) => x
val f2 = (x: Int, y: Int) => x + y
//证明1.函数可以调用方法--推出-->函数是对象
println(f1)//<function1>//直接打印函数,如果是对象的话,根据以前的经验,应该会调用对象的toString方法
println(f2)//<function2>
println(f1.toString())//<function1>//函数确实可以调用toString方法,那么如果和上面的打印结果一样,那么不就证明了我们的观点么!
println(f2.toString())//<function2>
//证明2.函数可以作为值赋值给变量接收--推出-->函数是对象
val f3 = f2 //f2是上面定义好的函数,现在赋值给f3变量了, 那么f3变量也成了函数
println(f3)//<function2>
//证明3.函数可以当作参数被传递给方法--推出-->函数是对象
val f4 = (x: Int, y: Int) => x + y
//val result: Int = myMethod(1,2,f4) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
//也可以传递匿名函数
val result: Int = myMethod(1,2,(x: Int, y: Int) => x + y) //f4是一个函数,现在被当作参数传递给了myMethod方法的fun参数了
println(result)//3
val result2: Int = f4(1,2)
println(result2)//3
}
//定义一个方法,该方法接收2个int值,和1个函数,并在方法体中调用该函数,将2个int值传个该函数
def myMethod(a:Int,b:Int,fun:(Int, Int) => Int):Int ={
fun(a,b)//在方法体中调用函数,并将函数的计算结果作为myMethod方法的返回值
}
}
- 再复习一下
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 9:52
* Desc 演示高阶函数--其实就是函数的一些高级用法
*/
object FunctionDemo1 {
def main(args: Array[String]): Unit = {
//准备数据
val list = List(1,2,3,4,5)
//定义一个函数,将传入的参数扩大10倍
val fun = (i:Int) => {
i * 10}
//1.函数作为值传递给另一个变量
val f = fun
//2.函数作为参数,传递给方法
val res: List[Int] = list.map(f)
println(res)//List(10, 20, 30, 40, 50)
//3.匿名函数,就是将没有定义名称的函数直接传递给方法
//val res2: List[Int] = list.map((i:Int) => {i * 10})//传入匿名函数
//val res2: List[Int] = list.map(i => i * 10)//传入简写的匿名函数
val res2: List[Int] = list.map(_*10)//传入简写的匿名函数
println(res2)
//4.定义一个方法并转换为函数然后进行传递
def m(i:Int):Int={
i*10
}
val f2 = m _ //方法转为函数
val res3: List[Int] = list.map(f2)
println(res3)
//5.直接传递方法给函数型参数,编译器会自动将方法转为函数
val res4: List[Int] = list.map(m)
println(res4)
//类似于
//list.foreach(i=>println(i))
//list.foreach(println _)
list.foreach(println)
}
}
闭包-★
Scala中的闭包
- 什么是闭包
- 闭包其实就是一个函数,只不过该函数依赖于声明在函数外部的一个或多个变量
- 就是一个函数把外部的那些不属于自己的变量也包含(闭合)进来。
- 闭包会导致什么
- 可能会产生多次调用,结果不一致
- 怎么解决闭包出现的问题
- 1.避免闭包出现(尽量不在函数内使用外部变量)
- 2.定义变量使用val
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:18
* Desc 演示Scala函数的闭包
*/
object FunctionDemo2 {
def main(args: Array[String]): Unit = {
//定义一个变量,后续可以提供给函数使用
var b = 10 //建议使用val修饰
//定义一个函数,传入a并和函数外的变量b相加
val add = (a: Int) => {
a + b
}
//调用函数
val res1: Int = add(1)
println(res1) //11
//修改b的值
b = 100
//再次调用函数,传入同样的值
val res2: Int = add(1)
println(res2)//101
//观察上面的运行结果,我们会发现:
//两次调用同一个函数add,传入通用的参数1,得到的结果尽然不同!
//原因是因为函数add中用到了外部的不属于该add函数的变量b,形成了闭包现象
//而b的值不受函数add的控制,在外部可能会发生改变,所以add函数的两次调用结果不一样!
//问题如何解决?
//1.尽量避免使用闭包(也就是尽量不要在函数内部使用不属于该函数的变量)
//2.如果避免不了使用闭包,那么将用到的不属于该函数的变量用val修饰,如 val b = 10
//总结:闭包就是函数内部使用了外部的变量,闭包可能会产生多次调用结果不一致的情况,可以通过避免使用闭包或使用val修饰变量来解决
}
}
- 总结:
- 闭包就是
函数内部使用了外部的变量
, - 闭包
可能会产生多次调用结果不一致的情况
, - 可以通过避免使用闭包或使用
val修饰变量
来解决
- 闭包就是
扩展-Java中的闭包-了解即可
package cn.hanjiaxiaozhi.highfunction;
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:29
* Desc 演示Java中的闭包
*/
public class FunctionDemo_Java {
//Scala中的闭包指的是函数用到了外部的变量
//函数的本质是对象
//所以Java中的闭包指的是匿名内部类对象中使用到了外部的变量!(因为Java中函数的本质就是匿名内部类对象)
public static void main(String[] args) {
/*final*/ int b = 10;
int res1 = testAdd(1, new Fun() {
@Override
public int add(int a) {
return a + b;
//注意:匿名内部类对象中使用到了外部的变量,称作闭包
//注意:Java中的闭包,会自动将使用到的外部变量加final修饰
}
});
System.out.println(res1);//11
//尝试修改b的值
//b = 100;//这里不能对b就行修改,因为是闭包中使用的变量,默认已经加final了
//testAdd(1,(int a)->{return a+b;});
int res2 = testAdd(1, a -> a + b);
System.out.println(res2);//11
//总结:Java中的闭包,其实就是匿名内部类/函数中使用到了外部变量
//但是在Java中会把在闭包中用到的外部变量自动加final修饰,所以不能修改,也就避免了一些问题
}
//定义一个方法,接收一个int a和一个Fun类型的对象,并在方法体中调用fun.add(a)
public static int testAdd(int a, Fun fun) {
return fun.add(a);
}
}
interface Fun {
int add(int a);
}
柯里化
- 引入
- Scala中一个方法/函数可以多次调用,
每次只传递部分参数,返回一个函数,后面接着调用返回的函数传递其他的参数
- 这样就可以方便地绑定一些参数,其余的参数可稍后再补上,这其实就是柯里化的思想
- 在scala和spark的源代码中,大量使用到了柯里化。为了后续方便阅读源代码,我们需要来了解一下柯里化。
- 什么是柯里化Currying
- 柯里化指的是:把函数/方法的一个参数列表接受多个参数 变换成 多个参数列表的过程(每次传入一个参数会返回一个新函数接受余下的参数)
- 例如:fun(a:Int,b:Int)变成fun(a:Int)(b:Int)
- 柯里化的意义
- https://www.zhihu.com/question/20037482
- 函数柯里化之后,可以只提供一个参数,其他的参数作为这个函数的“环境”来创建。这就能让函数回归到原始的:一个参数进去一个值出来的状态。
- 柯里化又可叫
部分求值
。一个柯里化的函数接收一些参数,接收了这些参数之后,该函数并不是立即求值,而是继续返回另一个函数,刚才传入的参数在函数形成的闭包中被保存起来,待到函数真正需要求值的时候,之前传入的所有参数都能用于求值。 - 使用柯里化
可以简化主函数的复杂度,提高主函数的自闭性,使代码模块化,减少耦合增强其可维护性,提高功能上的可扩张性、灵活性。可以编写出更加抽象、功能化和高效的代码
。 - 柯里化是以函数为主体的编程语言思想发展的必然产生的结果。
- Scala中的很的地方都用到了柯里化,
如fold
- 柯里化的作用:
可以对参数进行分批/归类传递
package cn.hanjiaxiaozhi.highfunction
/**
* Author hanjiaxiaozhi
* Date 2020/7/19 10:18
* Desc 演示Scala中的柯里化
*/
object FunctionDemo3 {
def main(args: Array[String]): Unit = {
println(add1(1, 2))//3
println(add2(1)(2))//3
//注意:柯里化的方法需要的多个参数可以分开传递
//前几次传递会返回一个函数,直到最后一次传递返回方法计算的结果
val tempFun: Int => Int = add2(1) //(b:Int) => 1 + b
val res: Int = tempFun(2)
//上面的分开调用和下面的等价
val myTempFun:Int => Int = (b:Int) => 1 + b
val res2: Int = myTempFun(2)
println(res)//3
println(res2)//3
}
//定义一个普通的方法,实现2个数相加
def add1(a:Int,b:Int):Int={
a + b
}
//定义一个柯里化的方法,实现2个数相加
//柯里化指的是:将一次接收多个参数的方法转为了分多次接收
def add2(a:Int)(b:Int):Int={
a + b
}
//柯里化的作用:可以对参数进行分批/归类传递
//如之前学习的 fold/foldLeft方法
//fold(初始值)(函数)
//val list = List[Int](1, 2, 3, 4, 5, 6, 7, 8, 9)//和为45
//需求对list中的元素求和,并给定初始值
//val res1: Int = list.fold(0)(_+_)
//上面的fold就是典型的柯里化方式定义的方法
//传递参数的时候,很容易进行区分,第一个是括号里的初始化值,第二个括号里的是函数
//当然我们以后开发自己很少写这样的,都是源码中会偶尔定义这样的柯里化方法
//我们调用的时候知道该方法是一个柯里化方法即可
}