1 高阶函数
Scala 混合了面向对象和函数式的特性,在函数式编程语言中,函数是“头等公民”,它和Int、String、Class等其他类型处于同等的地位,可以像其他任何数据类型一样被传递和操作。
高阶函数包含:作为值的函数、匿名函数、闭包、柯里化等等。
1.1 作为值的函数
在scala中,函数就像和数字、字符串一样,可以将函数传递给一个方法。我们可以对算法进行封装,然后将具体的动作传递给算法,这种特性很有用。
我们之前学习过List的map方法,它就可以接收一个函数,完成List的转换。
示例:将一个小数列表中的每个元素转换为对应个数的小星星
List(1, 2, 3…) => *, **, **
代码:
val list = List(1, 2, 3, 4) // 字符串*方法,表示生成指定数量的字符串 val func_num2star = (num:Int) => "*" * num print(list.map(func_num2star))
1.2 匿名函数
上面的代码,给(num:Int) => “*” * num函数赋值给了一个变量,但是这种写法有一些啰嗦。在scala中,可以不需要给函数赋值给变量,没有赋值给变量的函数就是匿名函数
示例:优化上述代码
val list = List(1, 2, 3, 4) list.map(num => "*" * num).foreach(println) // 因为此处num变量只使用了一次,而且只是进行简单的计算,所以可以省略参数列表,使用_替代参数 list.map("*" * _).foreach(println)
1.3 柯里化
柯里化(Currying)是指将原先接受多个参数的函数多个一个参数的多参数列表的过程。
柯里化过程解析
使用柯里化,让传递匿名函数作为参数的语法更为简洁
示例:编写一个泛型方法,用来完成两个值类型的计算(具体的计算封装到函数中)
object CurryingDemo2 { // 实现对两个数进行计算的方法 def calc[A <: AnyVal](x:A, y:A, func_calc:(A, A)=>A) = { func_calc(x, y) } // 柯里化:实现对两个数进行计算 def calc_carried[A <: AnyVal](x:A, y:A)(func_calc:(A, A)=>A) = { func_calc(x, y) } def main(args: Array[String]): Unit = { // 这种写法是不能被简化的,必须要写出函数的定义 println(calc(10, 10, (x:Int, y:Int)=> x + y)) println(calc(10.1, 10.2, (x:Double, y:Double)=> x*y)) // 柯里化之后可以快乐地使用下划线了 println(calc_carried(10, 10)(_ + _)) println(calc_carried(10.1, 10.2)(_ * _)) println(calc_carried(100.2, 10)(_ - _)) } }
1.4 闭包
闭包其实就是一个函数,只不过这个函数的返回值依赖于声明在函数外部的变量。
可以简单认为,就是可以访问不在当前作用域范围的一个函数。
示例:定义一个闭包
object ClosureDemo { def add(x:Int) = { val y = 10 // add返回一个函数,该函数引用了add方法的一个局部变量 val funcAdd = () => x + y funcAdd } def main(args: Array[String]): Unit = { // 调用add方法时,任然可以引用到y的值 // funcAdd函数就是一个闭包 println(add(10)()) } }
2 隐式转换和隐式参数
隐式转换和隐式参数是scala非常有特色的功能,也是Java等其他编程语言没有的功能。我们可以很方便地利用隐式转换来丰富现有类的功能。
2.1 隐式转换
来看一个案例,
object SuperIntDemo { def main(args: Array[String]): Unit = { val a:Int = 1 // 使用中缀调用to方法 println(a to 10) } }
通过查看Int的源代码,你会惊奇地发现,Int类型根本没有to方法。这难道是让人怀疑人生的大bug吗?
——这其实就是隐式转换的强(gui)大(yi)之处。它在背后偷偷摸摸地帮我们了某种类型转换。
所谓隐式转换,是指以implicit关键字声明的带有单个参数的方法。它是自动被调用的,自动将某种类型转换为另外一种类型。
隐式转换的使用步骤:
在object中定义隐式转换方法(使用implicit)
在需要用到隐式转换的地方,引入隐式转换(使用import)
自动调用隐式转化后的方法
示例:使用隐式转换,让File具备有reada
class RichFile(val f:File) { // 将文件中内容读取成字符串 def read() = Source.fromFile(f).mkString } object MyPredef { // 定义隐式转换方法 implicit def file2RichFile(f:File) = new RichFile(f) } object ImplicitConvertDemo { def main(args: Array[String]): Unit = { val f = new File("./data/textfiles/1.txt") // 导入隐式准换 import MyPredef.file2RichFile // 调用的其实是RichFile的read方法 println(f.read()) } }
2.2 自动导入隐式转换方法
前面,我们手动使用了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.3 隐式转换的时机
什么时候会自动执行隐式转换呢?
- 当对象调用中不存在的方法时,编译器会自动将对象进行隐式转换
- 当方法中的参数类型与目标类型不一致时
示例:
object ImplicitConvertDemo { // 定义隐式转换方法 implicit def file2RichFile(f:File) = new RichFile(f) def main(args: Array[String]): Unit = { val f = new File("./data/textfiles/1.txt") // test1接收的参数类型为Rich,此时也自动进行了隐式转换 test1(f) } def test1(r:RichFile) = println(r.read()) }
2.4 隐式参数
函数或方法可以带有一个标记为implicit的参数列表。这种情况,编译器会查找缺省值,提供给该方法。
定义隐式参数:
- 在方法后面添加一个参数列表,参数使用implicit修饰
- 在object中定义implicit修饰的隐式值
- 调用方法,可以不传入implicit修饰的参数列表,编译器会自动查找缺省值
示例:
// 定义一个分隔符类 case class Delimiters(left:String, right:String) object MyPredef1 { implicit val quoteDelimiters = Delimiters("<<<", ">>>") } object ImplicitParamDemo { // 使用分隔符将想要引用的字符串括起来 def quote(what:String)(implicit delims:Delimiters) = delims.left + what + delims.right def main(args: Array[String]): Unit = { println(quote("hello, world")(Delimiters("<<", ">>"))) // 手动导入 import MyPredef1._ println(quote("hello, world")) } }
- 和隐式转换一样,可以使用import手动导入隐式参数
- 如果在当前作用域定义了隐式值,会自动进行导入
3 Akka RPC框架入门
Akka简介
Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用的工具包。Akka是使用scala开发的库,同时可以使用scala和Java语言来开发基于Akka的应用程序。
3.1 Akka特性
- 提供基于异步非阻塞、高性能的事件驱动编程模型
- 内置容错机制,允许Actor在出错时进行恢复或者重置操作
- 超级轻量级的事件处理(每GB堆内存几百万Actor)
- 使用Akka可以在单机上构建高并发程序,也可以在网络中构建分布式程序。
3.2 Akka Actor并发编程模型
以下图片说明了Akka Actor的并发编程模型的基本流程:
- 学生创建一个ActorSystem
- 通过ActorSystem来创建一个ActorRef(老师的引用),并将消息发送给ActorRef
- ActorRef将消息发送给Message Dispatcher(消息分发器)
- Message Dispatcher将消息按照顺序保存到目标Actor的MailBox中
- Message Dispatcher将MailBox放到一个线程中
- MailBox按照顺序取出消息,最终将它递给TeacherActor接受的方法中
3.3 入门案例
案例:基于Akka创建两个Actor,Actor之间可以互相发送消息。
实现步骤:
- IDEA创建Maven项目
- 选择Maven项目,添加scala支持
- 导入Akka Maven依赖
- 创建两个Actor
- 创建、发送消息
- 启动测试
具体实现:
- 创建Maven项目,添加scala支持
打开pom.xml文件,导入akka Maven依赖和插件
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <encoding>UTF-8</encoding> <scala.version>2.11.8</scala.version> <scala.compat.version>2.11</scala.compat.version> </properties> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${scala.version}</version> </dependency> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-actor_2.11</artifactId> <version>2.3.14</version> </dependency> <dependency> <groupId>com.typesafe.akka</groupId> <artifactId>akka-remote_2.11</artifactId> <version>2.3.14</version> </dependency> </dependencies> <build> <sourceDirectory>src/main/scala</sourceDirectory> <testSourceDirectory>src/test/scala</testSourceDirectory> <plugins> <plugin> <groupId>net.alchim31.maven</groupId> <artifactId>scala-maven-plugin</artifactId> <version>3.2.2</version> <executions> <execution> <goals> <goal>compile</goal> <goal>testCompile</goal> </goals> <configuration> <args> <arg>-dependencyfile</arg> <arg>${project.build.directory}/.scala_dependencies</arg> </args> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer"> <resource>reference.conf</resource> </transformer> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass></mainClass> </transformer> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build>
- 创建两个Actor
- SenderActor:用来发送消息
- ReceiveActor:用来接收,回复消息
- 创建、发送消息
- 使用样例类封装消息
- SubmitTaskMessage——提交任务消息
- SuccessSubmitTaskMessage——任务提交成功消息
- 使用类似于之前学习的Actor方式,使用
!
发送异步消息
- 启动测试