Scala:高阶函数、隐式转换
课程目标
- 理解高阶函数的概念(作为值的函数、匿名函数、闭包、柯里化)
- 掌握隐式转换和隐式参数
- 掌握Akka并发编程框架
1. 高阶函数
scala 混合了面向对象和函数式的特性,在函数式编程语言中,函数是“头等公民”,它和Int、String、Class等其他类型处于同等的地位,可以像其他类型的变量一样被传递和操作。
高阶函数包含
- 作为值的函数
- 匿名函数
- 闭包
- 柯里化等等
1.1 作为值的函数
在scala中,函数就像和数字、字符串一样,可以将函数传递给一个方法。我们可以对算法进行封装,然后将具体的动作传递给方法,这种特性很有用。
我们之前学习过List的map方法,它就可以接收一个函数,完成List的转换。
示例
示例说明
将一个整数列表中的每个元素转换为对应个数的小星星
List(1, 2, 3...) => *, **, ***
步骤
- 创建一个函数,用于将数字装换为指定个数的小星星
- 创建一个列表,调用map方法
- 打印转换为的列表
参考代码
val func: Int => String = (num:Int) => "*" * num println((1 to 10).map(func))
1.2 匿名函数
定义
上面的代码,给(num:Int) => “*” * num函数赋值给了一个变量,但是这种写法有一些啰嗦。在scala中,可以不需要给函数赋值给变量,没有赋值给变量的函数就是匿名函数
val list = List(1, 2, 3, 4) // 字符串*方法,表示生成指定数量的字符串 val func_num2star = (num:Int) => "*" * num print(list.map(func_num2star))
示例
使用匿名函数优化上述代码
参考代码
println((1 to 10).map(num => "*" * num)) // 因为此处num变量只使用了一次,而且只是进行简单的计算,所以可以省略参数列表,使用_替代参数 println((1 to 10).map("*" * _))
1.3 柯里化
在scala和spark的源代码中,大量使用到了柯里化。为了后续方便阅读源代码,我们需要来了解下柯里化。
定义
柯里化(Currying)是指将原先接受多个参数的方法转换为多个只有一个参数的参数列表的过程。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yitd5lcH-1625207556241)(/assets/1552811606951.png)]
柯里化过程解析
示例
示例说明
- 编写一个方法,用来完成两个Int类型数字的计算
- 具体如何计算封装到函数中
- 使用柯里化来实现上述操作
参考代码
// 柯里化:实现对两个数进行计算 def calc_carried(x:Double, y:Double)(func_calc:(Double, Double)=>Double) = { func_calc(x, y) } def main(args: Intrray[String]): Unit = { println(calc_carried(10.1, 10.2){ (x,y) => x + y }) println(calc_carried(10, 10)(_ + _)) println(calc_carried(10.1, 10.2)(_ * _)) println(calc_carried(100.2, 10)(_ - _)) }
1.4 闭包
闭包其实就是一个函数,只不过这个函数的返回值依赖于声明在函数外部的变量。
可以简单认为,就是可以访问不在当前作用域范围的一个函数。
示例一
定义一个闭包
val y=10 val add=(x:Int)=>{ x+y } println(add(5)) // 结果15
add函数就是一个闭包
示例二
柯里化就是一个闭包
def add(x:Int)(y:Int) = { x + y }
上述代码相当于
def add(x:Int) = { (y:Int) => x + y }
2. 隐式转换和隐式参数
隐式转换和隐式参数是scala非常有特色的功能,也是Java等其他编程语言没有的功能。我们可以很方便地利用隐式转换来丰富现有类的功能。后面在编写Akka并发编程、Spark SQL、Flink都会看到隐式转换和隐式参数的身影。
2.1 定义
所谓隐式转换,是指以implicit关键字声明的带有单个参数的方法。它是自动被调用的,自动将某种类型转换为另外一种类型。
使用步骤
- 在object中定义隐式转换方法(使用implicit)
- 在需要用到隐式转换的地方,引入隐式转换(使用import)
- 自动调用隐式转化后的方法
示例
示例说明
使用隐式转换,让File具备有read功能——实现将文本中的内容以字符串形式读取出来
步骤
- 创建RichFile类,提供一个read方法,用于将文件内容读取为字符串
- 定义一个隐式转换方法,将File隐式转换为RichFile对象
- 创建一个File,导入隐式转换,调用File的read方法
参考代码
class RichFile(val file:File) { // 读取文件为字符串 def read() = { Source.fromFile(file).mkString } } object RichFile { // 定义隐式转换方法 implicit def file2RichFile(file:File) = new RichFile(file) } def main(args: Array[String]): Unit = { // 加载文件 val file = new File("./data/1.txt") // 导入隐式转换 import RichFile.file2RichFile // file对象具备有read方法 println(file.read()) }
2.2 隐式转换的时机
- 当对象调用类中不存在的方法或者成员时,编译器会自动将对象进行隐式转换
- 当方法中的参数的类型与目标类型不一致时
2.3 自动导入隐式转换方法
前面,我们手动使用了import来导入隐式转换。是否可以不手动import呢?
在scala中,如果在当前作用域中有隐式转换方法,会自动导入隐式转换。
示例:将隐式转换方法定义在main所在的object中
class RichFile(val f:File) { // 将文件中内容读取成字符串 def read() = Source.fromFile(f).mkString } object ImplicitConvertDemo { // 定义隐式转换方法 implicit def file2RichFile(f:File) = new RichFile(f) def main(args: Array[String]): Unit = { val f = new File("./data/textfiles/1.txt") // 调用的其实是RichFile的read方法 println(f.read()) } }
2.4 隐式参数
方法可以带有一个标记为implicit的参数列表。这种情况,编译器会查找缺省值,提供给该方法。
定义
- 在方法后面添加一个参数列表,参数使用implicit修饰
- 在object中定义implicit修饰的隐式值
- 调用方法,可以不传入implicit修饰的参数列表,编译器会自动查找缺省值
[!NOTE]
- 和隐式转换一样,可以使用import手动导入隐式参数
- 如果在当前作用域定义了隐式值,会自动进行导入
示例
示例说明
- 定义一个方法,可将传入的值,使用一个分隔符前缀、后缀包括起来
- 使用隐式参数定义分隔符
- 调用该方法,并打印测试
参考代码
// 使用implicit定义一个参数 def quote(what:String)(implicit delimiter:(String, String)) = { delimiter._1 + what + delimiter._2 } // 隐式参数 object ImplicitParam { implicit val DEFAULT_DELIMITERS = ("<<<", ">>>") } def main(args: Array[String]): Unit = { // 导入隐式参数 import ImplicitParam.DEFAULT_DELIMITERS println(quote("李雷和韩梅梅")) }
elimiter:(String, String)) = { delimiter._1 + what + delimiter._2 } // 隐式参数 object ImplicitParam { implicit val DEFAULT_DELIMITERS = ("<<<", “>>>”) } def main(args: Array[String]): Unit = { // 导入隐式参数 import ImplicitParam.DEFAULT_DELIMITERS
println(quote("李雷和韩梅梅"))
}