友情推荐
参考
Scala 参考文档 (scala-lang.org)
[Scala 教程 ](
Option 类型
使用 Option
类型,可以用来有效避免空引用(null)异常。也就是说,将来我们返回某些数据时,可以 返回一个 Option
类型来替代
即:使用 Option
来封装返回结果
① Some(X):表示实际的值
final case class Some[+A](val value : A) extends scala.Option[A]{
def isEmpty : scala.Boolean = false
def get : A = x
}
② None:表示没有值
case object None extends scala.Option[scala.Nothing] {
def isEmpty : scala.Boolean = true
def get : scala.Nothing = throw new NoSuchElementException("None.get")
}
注:使用 getOrElse()
方法,当值为 None
是可以指定一个默认值
案例一:定义一个两个数相除的方法,使用 Option
类型来封装结果
def dvi(a:Double, b:Double):Option[Double] = {
if(b != 0) {
Some(a / b)
}
else {
None
}
}
def main(args: Array[String]): Unit = {
val result1 = dvi(1.0, 5)
result1 match {
case Some(x) => println(x)
case None => println("除零异常")
}
}
案例二:使用 getOrElse
方法,当除零时,或者默认值为 0
def dvi(a:Double, b:Double) = {
if(b != 0) {
Some(a / b)
}
else {
None
}
}
def main(args: Array[String]): Unit = {
val result = dvi(1, 0).getOrElse(0)
println(result)
}
偏函数
偏函数可以提供了简洁的语法,可以简化函数的定义,配合集合的函数式编程,可以让代码更加优雅
① 被包在花括号内没有 match
的一组 case
语句是一个偏函数
② 偏函数是 PartialFunction[A, B]
的一个实例
A代表输入参数类型
B代表返回结果类型
// 格式一
val func1: PartialFunction[A, A] = {
case xxx => xxx
case xxx => xxx
case xxx => xxx
case _ => "其他"
}
// 格式二
{
case xxx => xxx
case xxx => xxx
case xxx => xxx
case _ => "其他"
}
示例一
// func1是一个输入参数为Int类型,返回值为String类型的偏函数
val func1: PartialFunction[Int, String] = {
case 1 => "一"
case 2 => "二"
case 3 => "三"
case _ => "其他"
}
println(func1(2))
示例二
val list = (1 to 10).toList
val list2 = list.map({
case x if x >= 1 && x <= 3 => "[1-3]"
case x if x >= 4 && x <= 8 => "[4-8]"
case x if x > 8 => "(9-*]"
})
println(list2)
正则表达式
在 Scala 中,可以很方便地使用正则表达式来匹配数据
① Scala 中提供了 Regex
类来定义正则表达式
② 要构造一个 RegEx
对象,直接使用 String
类的 r
方法即可
③ 建议使用三个双引号来表示正则表达式,不然就得对正则中的反斜杠来进行转义
// 定义表达式
val regEx:RexEx = """正则表达式""".r
// 获取匹配项
regEx.findAllMatchIn(目标字符串):集合
// 正则表达式判断字符串是否包含至少一个数字
val regex: Regex = """[0-9]""".r
val test = "abc5"
if (regex.findAllMatchIn(test).nonEmpty) {
println("字符串正确,包含数字!")
}
Actor(并发编程模型)
Actor 并发编程模型与 Java 并发编程模型对比
Actor 并发编程模型与 Java 并发编程模型不同,是一种基于事件模型的并发机制。该模型是一种不共享数据,依赖消息传递的一种并发编程模式,有效避免资源争夺、死锁等情况
Java内置线程模型 | scala Actor模型 |
---|---|
"共享数据-锁"模型 (share data and lock) | share nothing |
每个object有一个monitor,监视线程对共享数据的访问 | 不共享数据,Actor之间通过Message通讯 |
加锁代码使用synchronized标识 | |
死锁问题 | |
每个线程内部是顺序执行的 | 每个Actor内部是顺序执行的 |
注意:Scala 在 2.11.x
版本中加入了 Akka
并发编程框架,Actor
可以让我们更好了解 Akka
创建 Actor
① class 继承 Actor
import scala.actors.Actor
// 多个线程打印 1-20
class Actor1 extends Actor {
override def act(): Unit = (1 to 10).foreach(println(_))
}
class Actor2 extends Actor {
override def act(): Unit = (11 to 20).foreach(println(_))
}
def main(args: Array[String]): Unit = {
new Actor1().start()
new Actor2().start()
}
② Object 继承 Actor
object Actor1 extends Actor {
override def act(): Unit =
for(i <- 1 to 10) {
println(i)
}
}
object Actor2 extends Actor {
override def act(): Unit =
for(i <- 11 to 20) {
println(i)
}
}
def main(args: Array[String]): Unit = {
Actor1.start()
Actor2.start()
}
Actor 运行流程
① 调用 start()
方法启动 Actor
② 自动执行 act()
方法
③ 向 Actor
发送消息
④ act
方法执行完成后,程序会调用 exit()
方法
Actor 发送消息/接收消息
! | 发送异步消息,没有返回值 |
---|---|
!? | 发送同步消息,等待返回值 |
!! | 发送异步消息,返回值是Future[Any] |
object ActorSender extends Actor {
override def act(): Unit = {
// 发送字符串消息给Actor2
val msg = "我是 ActorSender"
println(s"ActorSender: 发送消息$msg")
ActorReceiver ! msg
// 再次发送一条消息,ActorReceiver无法接收到
ActorReceiver ! "你叫什么名字?"
}
}
object ActorReceiver extends Actor {
override def act(): Unit =
receive {
case msg: String => println(s"接收Actor: 接收到$msg")
}
}
def main(args: Array[String]): Unit = {
ActorSender.start()
ActorReceiver.start()
}
持续接收消息
使用 while(true)
循环,不停地调用 receive
来接收消息
① 如果当前 Actor 没有接收到消息,线程就会处于阻塞状态
② 如果有很多的 Actor,就有可能会导致很多线程都是处于阻塞状态
③ 每次有新的消息来时,重新创建线程来处理
④ 频繁的线程创建、销毁和切换,会影响运行效率
object ActorSender extends Actor {
override def act(): Unit = {
// 发送消息
while(true) {
ActorReceiver ! "ping!"
TimeUnit.SECONDS.sleep(3)
}
}
}
object ActorReceiver extends Actor {
override def act(): Unit = {
// 持续接收消息
while(true) {
receive {
case msg:String => println("接收到消息:" + msg)
}
}
}
}
def main(args: Array[String]): Unit = {
ActorReceiver.start()
ActorSender.start()
}
持续接受消息优化
在 Scala
中,可以使用 loop + react
来复用线程,比 while + receive
更高效
loop {
react {
case msg:String => println("接收到消息:" + msg)
}
}
高阶函数
作为值的函数
在 Scala 中,函数就像和数字、字符串一样,可以将函数传递给一个方法。我们可以对算法进行封装,然后将具体的动作传递给方法
// 列表中的每个元素 +1
val func: Int => Int = (a: Int) => a + 1
println((1 to 10).map(func))
匿名函数
在 Scala 中,可以不需要给函数赋值给变量,没有赋值给变量的函数就是匿名函数
// 给集合中的每个元素 +1
println((1 to 10).map(_ + 1)
柯里化
在 Scala 和 Spark 的源代码中,大量使用到了柯里化
柯里化的定义
柯里化(Currying)是指将原先接受多个参数的方法转换为多个只有一个参数的参数列表的过程
def method ( a: Int , b:String )={}
def method(a: Int)( b:String)={}
柯里化实现
// 柯里化:实现对两个数进行计算
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)(_ - _))
}
闭包
闭包是一个函数,只不过这个函数的返回值依赖于声明在函数外部的变量,即可以不在当前作用域范围的一个函数
示例一
// 此处的 add 函数就是一个闭包
val y=10
val add=(x:Int)=>{
x+y
}
println(add(5)) // 结果15
示例二
def add(x:Int)(y:Int) = {
x + y
}
// 简写
def add(x:Int) = {
(y:Int) => x + y
}
隐式转换和隐式参数
隐式转换和隐式参数是 Scala 非常有特色的功能,也是 Java 等其他编程语言没有的功能,利用隐式转换来丰富现有类的功能
定义
隐式转换,是指以 implicit
关键字声明的带有单个参数的方法。它是自动被调用的,自动将某种类型转换为另外一种类型
① 在 object 中定义隐式转换方法(使用implicit)
② 在需要用到隐式转换的地方,手动引入隐式转换(使用import)
③ 编译器自动调用隐式转化后的方法
// 需求:使用隐式转换,让 java.io.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())
}
隐式转换的使用场景
① 当对象调用类中不存在的方法或者成员时,编译器会自动将对象进行隐式转换
② 当方法中的参数的类型与目标类型不一致时
自动导入隐式转换方法
在 Scala 中,如果在当前作用域中有隐式转换方法,会自动导入隐式转换
如果隐式转换方法定义在伴生对象中,在伴生对象中使用则不需要导入隐式转换方法
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())
}
}
隐式参数
方法可以带有一个标记为 implicit
的参数列表,这种情况,编译器会查找缺省值,提供给该方法
① 隐式参数一般用在柯里化方法
② 在方法后面添加一个参数列表,参数使用 implicit 修饰
③ 在 object 中定义 implicit 修饰的隐式值
④ 调用方法,可以不传入 implicit 修饰的参数列表,编译器会自动查找缺省值
// 1 定义一个柯里化方法quote(String)(Tuple),可将传入的值,使用一个分隔符前缀、后缀包括起来
def quote(str:String)( implicit delimiter:(String,String))=delimiter._1+str+delimiter._2
// 2 定义一个object,包含一个成员变量,用implicit修饰,表示隐式参数,也是步骤1的潜在的分隔符
object ImplicitParam{
implicit val DEFAULT_DELIMITERS=("<<<",">>>")
}
def main(args: Array[String]): Unit = {
// 3 调用该方法,并打印测试
import ImplicitParam.DEFAULT_DELIMITERS
println(quote("你好"))
}
注意1: 和隐式转换一样,可以使用 import 手动导入隐式参数
注意2: 如果在当前作用域定义了隐式值,会自动进行导入