1. Scala简介
1.1概述
Scala,它是一门基于JVM的多范式编程语言,通俗的说 Scala是一种运行在JVM上的函数式的面向对象语言。Scala可被广泛应用于各种编程任务。从编写小型脚本到构建巨型系统它都能胜任。正因如此 Scala得以提供一些出众的特性,例如它集成了面问对象编程和面向函数式编程的各种特性,以及更高层的并发模型。
基于JVM解释:Scala运行环境和Java类似,依赖于JVM
多范式解释:Scala支持多种编程风格
1.2语言特点
Scala是面向对象的
Scala是一种纯粹的面向对象语言,每一个值都是对象。对象的数据类型以及行为由类和特征来描述,类抽象机制的扩展通过两种途径实现,一种是子类继承,另一种是混入机制,这两种途径都能够避免多重继承的问题。
Scala是函数式编程的
Scala也是一种函数式语言,其函数可以作为值来使用。
Scala是兼容的
兼容Java,可以访问庞大的Java类库
Scala是精简的
Scala表达能力强,一行代码抵得上多行Java代码,开发速度快,可以让你的程序保持短小、清晰,看起来更简洁、更优雅
Scala是静态类型的
Scala拥有非常先进的静态类型系统,通过编译时检查,保证代码的安全性和一致性。支持:类型推断和模式匹配
Scala可以开发大教据应用程序
例如:Spark、Flink程序等等
1.3 Scala程序和Java程序对比
1.3.1程序的执行流出对比
1.3.2代码对比
Java代码
// 实体类
public class People {
private String name;
private int age;
public People(){}
public People(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 测试类
public class CodeDemo {
public static void main(String[] args) {
People people = new People("wang",20);
System.out.println(people);
}
}
Scala代码
case class People(var name:String, var age:Int)
val peolpe = People("wang",20);
println(peolpe);
1.4 Scala环境搭建
1.4.1 概述
Scala是基于java之上,大量使用java的类库和变量,必须要有Java运行环境,Scala才能正确执行,要编译运行Scala程序,需要:
JDK
Scala编译器( Scala SDK)
接下来,需要依次安装以下内容:
1. 安装JDK
2. 安装 Scala SDK
3. 在IDEA中安装 Scala插件
1.4.2 安装 JDK
安装JDK1.8 64位版本,并配置好环境变量,此过程略。
1.4.3 安装 Scala SDK
Scala SDK是 Scala 语言的编译器,要开发 scala程序,必须要先安装 Scala SDK,安装步骤(示例版本:scala -2.11.12):
1. 下载 Scala SDK
官方下载地址:https://www.scala-lang.org/download/
2. 安装 Scala SDK
2.1 双击scala -2.11.12.msi,将 scala安装在指定目录,傻瓜式安装,下一步下一步即可
2.2 安装路径要合法,不要出现中文,空格等特殊符号
3. 测试是否安装成功
3.1 打开控制台,输入: scala -version
Scala code runner version 2.11.12 -- Copyright 2002-2017, LAMP/EPFL
3.2 启动Scala 解释器,输入scala,验证1.3.2中的Scala代码执行结果
3.3 退出Scala 解释器,输入:quit或直接关闭控制台
1.4.4 安装 IDEA Scala插件
IDEA默认是不支持Scala程序开发的,所以需要在IDEA中安装 Scala插件,让它来支持 Scala语言。
首先下载指定版本 IDEA Scala插件:
- 下载的Scala插件必須和本地安装的IDEA版本一致
- 官方下载地址:https://plugins.jetbrains.com/plugin/1347-scala
然后在IDEA中配置Scala插件
打开File-Settings,在Plugines标签页面设置图标有个Install plugin from disk
找到下载的插件,点击OK,并重启IDEA
2.Scala语法
2.1 输出语句
方式一:换行输出
格式:println(打印到控制台的语句)
方式一:不换行输出
格式:print(打印到控制台的语句)
注意:不管是println()还是print(),都可以同时打印多个值,格式为:print(值1,值2,值3...)
2.2 分号
Scala语句,单行代码后的分号可写可不写,如果是多行代码写在一行,则中间的分号不能省略,最后一条代码的分号可省略不写。
示例:
println(123)
println(456);println(789)
2.3 变量的声明
格式:
Java变量定义
int a = 0;
在Scala中定义变量有两个关键字:val、var
val/var 变量名 : 变量类型 = 初始值
其中:
val定义的是不可重新赋值的变量,也就是自定义常量
var定义的是可重新赋值的变量
Scala更多的是采用常量,而不是变量来解决问题,这样带来的好处是可以减少多线程并发安全问题,特别适合用于多并发分布式的场景。
Scala支持自动类型推测,可以自动根据变量的值来推断变量的类型,使代码更加简洁。
参考代码:
scala> val name = "zhangsan"
name: String = zhangsan
scala> val age = 20
age: Int = 20
2.4 字符串
Scala提供了多种定义字符串的方式
2.4.1 双引号
语法:
val/var 变量名 = “字符串”
2.4.2 插值表达式
Scala中,可以使用插值表达式来定义字符串,有效避免大量字符串的拼接
语法:
val/var 变量名 = s”${变量/表达式}字符串”
说明:
·在定义字符串前添加s
·在字符串中,可以使用${}来引用变量或者写表达式
示例:
scala> val str1 = "a"
str1: String = a
scala> val str2 = "b"
str2: String = b
scala> val result = s"str1=${str1},str2=${str2}"
result: String = str1=a,str2=b
2.4.3 三引号
使用三引号定义字符串来保存大段的文本,可以包括任何字符而不需要转义
语法:
val/var 变量名 = “”“字符串”””
示例:
定义一个字符串,保存以下SQL语句
select * from scala_customer where id = 1
使用Java定义
String sql = "select \n" +
"\t\t* \n" +
"from \n" +
"\t\tscala_customer \n" +
"where \n" +
"\t\tid = 1";
System.out.println(sql);
使用Scala定义
val sql = """select
| *
|from
| scala_customer
|where
| id = 1"""
println(sql)
2.5 标识符
标识符概念
Scala对各种变量、方法、函数等命名时使用的字符序列称为标识符。
凡是自己可以起名字的地方都叫标识符。
标识符的命名规则
Scala中的标识符声明,基本和Java是一致的,但是细节上会有所变化。
1) 首字符为字母,后续字符任意字母和数字,美元符号,可后接下划线_
2) 数字不可以开头
3) 首字符为操作符(比如+ - * / ),后续字符也需跟操作符,至少一个
4) 操作符(比如+-*/)不能在标识符中间和最后
5) 用反引号(``)可以包括的任意字符串,即使是关键字也可以
标识符命名注意事项
包名:尽量采取有意义的包名,简短,有意义
变量名、函数名 、方法名 采用驼峰法。
“$”与”_”最好不要出现在标识符中。“$”字符也被当作是字母,但被保留作为scala编译器产生的标识符之用。用户程序里的标识符不应该包含“$”字符,尽管能够编译通过,但这样做有可能导致与scala编译器产生的标识符发生名称冲撞。
”_”在Scala中有重要用途,为了防止”_”用法混乱,一般不用于变量声明。
示例:
val ++ = "123"
val %@!#~ = "345"
val `public` = ""
2.6 数据类型
数据类 |
描述 |
Byte |
8位有符号补码整数。数值区间为 -128 到 127 |
Short |
16位有符号补码整数。数值区间为 -32768 到 32767 |
Int |
32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
Long |
64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
Float |
32 位, IEEE 754 标准的单精度浮点数 |
Double |
64 位 IEEE 754 标准的双精度浮点数 |
Char |
16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
String |
字符序列 |
Boolean |
true或false |
层次结构图
类型 |
描述 |
Any |
Any是所有类型的超类型,也称为顶级类型。它定义了一些通用的方法如equals、hashCode和toString。 |
AnyVal |
所有数值类型的父类 |
AnyRef |
所有对象类型(引用类型)的父类 |
Unit |
表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
Null |
Null是AnyRef的子类,它的实例是null,null 可以赋值给任何对象类型。 |
Nothing |
Nothing类型在Scala的类层级的最底端;它是任何其他类型的子类型。 |
2.7 运算符
在Scala中,运算符即是方法,Scala 包含的运算符有以下几种类型:
算术运算符
关系运算符
逻辑运算符
位运算符
赋值运算符
2.7.1 算术运算符
运算符 |
运算 |
范例 |
结果 |
+ |
正号 |
+3 |
3 |
- |
负号 |
b=4; -b |
-4 |
+ |
加 |
5+5 |
10 |
- |
减 |
6-4 |
2 |
* |
乘 |
3*4 |
12 |
/ |
除 |
5/5 |
1 |
% |
取模(取余) |
7%5 |
2 |
+ |
字符串相加 |
“He”+”llo” |
“Hello” |
与Java不同的是,Scala中没有++,--这两个运算符,可以通过+=、-=来实现同样的效果。
2.7.2 关系运算符
运算符 |
运算 |
范例 |
结果 |
== |
相等于 |
4==3 |
FALSE |
!= |
不等于 |
4!=3 |
TRUE |
< |
小于 |
4<3 |
FALSE |
> |
大于 |
4>3 |
TRUE |
<= |
小于等于 |
4<=3 |
FALSE |
>= |
大于等于 |
4>=3 |
TRUE |
Java和Scala中关于==的区别
需求描述 |
Scala代码 |
Java代码 |
比较数据 |
== 或者 != |
equals()方法 |
比较引用 |
eq方法 |
== 或者 != |
示例:
val s1 = "abc"
val s2 = new String("abc")
println(s1 == s2)
println(s1.eq(s2))
2.7.3 逻辑运算符
用于连接多个条件(一般来讲就是关系表达式),最终的结果也是一个Boolean值。
假定:变量A为true,B为false
运算符 |
描述 |
实例 |
&& |
逻辑与 |
(A && B) 运算结果为 false |
|| |
逻辑或 |
(A || B) 运算结果为 true |
! |
逻辑非 |
!(A && B) 运算结果为 true |
注意:
在Scala代码中,不能对一个Boolean类型的值进行连续取反操作,但是在Java中可以。
即:println(!!true)
这样写会报错,不支持这种写法。
2.7.4 赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量。
运算符 |
描述 |
实例 |
= |
简单的赋值运算符,将一个表达式的值赋给一个左值 |
C = A + B 将 A + B 表达式结果赋值给 C |
+= |
相加后再赋值 |
C += A 等于 C = C + A |
-= |
相减后再赋值 |
C -= A 等于 C = C - A |
*= |
相乘后再赋值 |
C *= A 等于 C = C * A |
/= |
相除后再赋值 |
C /= A 等于 C = C / A |
%= |
求余后再赋值 |
C %= A 等于 C = C % A |
<<= |
左移后赋值 |
C <<= 2等于 C = C << 2 |
>>= |
右移后赋值 |
C >>= 2 等于 C = C >> 2 |
&= |
按位与后赋值 |
C &= 2 等于 C = C & 2 |
^= |
按位异或后赋值 |
C ^= 2 等于 C = C ^ 2 |
|= |
按位或后赋值 |
C |= 2 等于 C = C | 2 |
2.7.5 位运算符
运算符 |
描述 |
实例 |
& |
按位与运算符 |
(a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| |
按位或运算符 |
(a | b) 输出结果 61 ,二进制解释: 0011 1101 |
^ |
按位异或运算符 |
(a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
~ |
按位取反运算符 |
(~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
<< |
左移动运算符 |
a << 2 输出结果 240 ,二进制解释: 1111 0000 |
>> |
右移动运算符 |
a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
>>> |
无符号右移 |
A >>>2 输出结果 15, 二进制解释: 0000 1111 |
2.8 流程控制、循环
2.8.1 if条件判断
注意事项:
- 在scala中if else语句是有返回值的,返回值就是代码块中的最后一行
- 在scala中无论是方法还是函数以及条件判断等返回值都不需要加return关键字
- 在scala中没有三元表达式,可以使用if表达式替代三元表达式
示例:
object Example5 {
def main(args: Array[String]): Unit = {
val num = 21
val name = if (num > 20) "小明" else "小红"
println("name=>" + name)
// 如果在if else语句中 返回值的类型是不一样,scala会自动推断两者的公共类型,作为变量类型
//Any
val name2 = if (num == 20) "小明" else 99
println("name2=>" + name2)
println("name2.getClass=>" + name2.getClass)
//如果if else 语句中缺少了else语句块,那么其实默认是Unit ,Unit 用()表示 ,类似于java中的void
val name3 = if (num > 20) "小明"
val name4 = if (num > 20) "小明" else ()
println(name3)
println(name4)
}
}
2.8.2 for循环
2.8.2.1 简单循环
格式:
for(i <- 表达式/数组/集合){
//逻辑代码
}
参考代码:
object Example7 {
def main(args: Array[String]): Unit = {
//使用to方法会产生一个连续不断的区间范围 [0-10] ,两边都包含
for (i <- 0 to 10) {
println("i=>" + i) // 0 - 10
}
//使用until方法会产生一个连续不断的区间范围 [0-10),不包含10
for (i <- 0 until 10) {
println("i=>" + i) // 0 - 9
}
//遍历字符串
for (s <- "xiaoming") {
println(s)
}
}
}
2.8.2.2 嵌套循环
传统方式
for (i <- 1 to 3){
for (j <- 1 to 5) if (j==5) println("*") else print("*")
}
Scala特有方式
for (i <- 1 to 3;j <- 1 to 5) if (j==5) println("*") else print("*")
2.8.2.3 守卫
在for循环中可以添加if判断语句,这个判断就称之为守卫。
语法:
for(i <- 表达式/数组/集合 if 表达式){
//逻辑代码
}
参考代码:
for (i <- 1 to 10 if (i % 3) == 0) println(i) // i = 3,6,9
2.8.2.4 推导式for循环
Scala中for循环是有返回值的,在for循环体中可以使用yield表达式构建一个集合,使用过yield的for表达式称之为推导式。
针对每一次for循环的迭代,yield会产生一个值,被循环记录下来(内部实现上,像是一个缓冲区),当循环结束后,会返回所有yield的值组成的集合,返回集合的类型与被遍历的集合类型是一致的。
for (e<-List(1,2,3)) { yield e*e } // 语法错误,yield不能在任何括号内
参考代码:
var v = for(i <- 1 to 10) yield i * 10
println(v)
2.8.3 while循环
Scala中的while和do while的使用和Java中基本上是一致的
2.8.4 break和continue
在Scala中,类似Java的break和continue关键字被移除了,需要用到scala.util.control包下Breaks类中的breakable和break()方法
2.8.4.1 break用法
导包
import scala.util.control.Breaks._
使用breakable将表达式包起来
在需要退出循环的地方添加break()方法
参考代码:
import scala.util.control.Breaks._
Breakable{
for (i <- 1 to 10){
if(i == 5) break() else println(i)
}
}
2.8.4.2 continue用法
continue的实现与break类似,但有不同之处,实现break是用Breakable{}将整个表达式包裹起来,而实现continue是用Breakable{}将表达式的循环体包裹起来。
参考代码:
import scala.util.control.Breaks._
for (i <- 1 to 10){
breakable{
if (i % 3 == 0 ) break()
else println(i)
}
}
2.9 方法与函数
方法格式:
def 方法名 (参数名:参数类型,参数名:参数类型) : 返回值类型 = {
//方法体
}
返回值类型可以省略(定义递归方法不能省略),由Scala编译器自动推断。
如果有返回值的类型,方法的最后一条语句的返回值,一定要和方法的返回值类型保持一致。
返回值可以不写return,默认就是{}表达式的值。
示例:
def getMax(a:Int ,b:Int): Int = {
if (a>b) a else b
}
2.9.1 方法参数
Scala中的方法参数使用比较灵活,它支持一下几种类型的参数:
1.默认参数
2.带名参数
3.变长参数
2.9.1.1 默认参数
定义方法时可以给参数默认值。
参考代码:
def getSum1(x:Int = 10 ,y:Int = 20) = {
x + y
}
2.9.1.2 带名参数
调用方法时,可以指定参数的名称进行调用。
参考代码:
val sum = getSum1(y=1)
2.9.1.3 变长参数
如果方法的参数不固定,可以将该方法的参数定义成变长参数。
格式:
def 方法名 (参数名:参数类型*) : 返回值类型 = {
//方法体
}
2.9.2 方法调用
2.9.2.1 后缀调用法
语法:
对象名.方法名(参数)
示例:
Math.abs(-1)
2.9.2.2 中缀调用法
语法:
对象名 方法名 参数
示例:
Math abs -1
2.9.2.3 花括号调用法
语法:
对象名.方法名{
//表达式1
//表达式2
}
示例:
Math.abs{-1}
方法只有一个参数才能使用花括号调用法
2.9.2.4 无括号调用法
如果方法没有参数,可以省略方法名后的括号
示例:
def say() = println("hello")
say
2.9.3 函数
格式:
val 变量名 = (参数名:参数类型,参数名:参数类型...) =>{
// 函数体
}
示例:
val f1 = (x:Int,y:Int) =>{
x + y
}
2.9.4 方法与函数的区别
在java中,方法和函数之间没有区别,只是叫法不同,在Scala中,函数和方法是有区别的:
1. Scala中使用def语句定义方法,val语句定义函数。
2. Scala中,在运行时,方法是加载到JVM的方法区中,函数是加载到JVM的堆内存中。
3. 方法不能作为单独的表达式而存在(参数为空的方法除外),而函数可以。
示例:
object Example14 {
// 定义一个有参方法
def m(x:Int) = 2*x
// 定义一个函数
val f = (x:Int) => 2*x
// 定义一个无参方法
def m1()=1+2
// 有参方法不能作为最终表达式出现
m
// 函数可以作为最终表达式出现
f
// 无参方法可以作为最终表达式出现
m1
}
4. 方法可以没有参数列表,参数列表也可以为空。但是函数必须有参数列表(也可以为空)。
示例:
// 方法可以没有参数列表
def m2 = 100
// 方法可以有一个空的参数列表
def m3() = 100
// 函数必须有参数列表,否则报错
var f1 = => 100
// 函数也可以有一个空的参数列表
var f2 = () => 100
5. 方法名意味着方法调用;但函数名只是代表函数自身。
6. 方法可以自动(称之ETA扩展)或手动强制转换为函数;但是函数不可以转换成方法
将方法转换为函数:
def m4( x : Int , y : Int ) = {
x + y
}
val f4 = m4 _
println(f4 (1,2))
2.10 类和对象
类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,占用存储空间。类是用于创建对象的蓝图,它是一个定义包括在特定类型的对象中的方法和变量的软件模板。
创建类和对象有简写的形式:
- 如果类是空的,没有任何成员,可以省略{}
- 如果构造器参数为空,可以省略()
示例:
object Example15 {
class People
def main(args: Array[String]): Unit = {
val p = new People
}
}
2.10.1 成员变量
可以使用 var/val 定义成员变量,对象通过 对象名. 的方式访问成员变量。
示例:
object Example16 {
class Person{
var name = ""
var age = 0
}
def main(args: Array[String]): Unit = {
val person = new Person
person.name = "张三"
person.age = 24
print(s"姓名:${person.name},年龄:${person.age}")
}
}
使用下划线初始化成员变量
在定义 var 类型的成员变量时,可以使用 _ 初始化成员变量。
String->null
Int->0
Boolean->false
Double->0.0
...
示例:
object Example16 {
var col1:String = _
var col2:Byte = _
var col3:Int = _
var col4:Long = _
var col5:Float = _
var col6:Double = _
var col7:Char = _
var col8:Boolean = _
}
val 类型的成员变量,必须要自己手动初始化
2.10.2 访问修饰符
在 Scala 中没有 public 关键字,没有被标记为 private 和 protected 的成员都是公共的。
Scala 中的权限修饰符只有:private、private[this]、protected、默认。
2.10.3 类的构造器
2.10.3.1 主构造器
语法:
class 类名(var/val 参数名:类型 = 默认值,...){
//构造代码块
}
说明:
主构造器与类名交织在一起,一个类只能定义一个主构造器。
创建对象的顺序一定是主构造器最先执行。
主构造器的参数会成为类的字段属性。
在主构造器参数中,没有被任何关键字修饰的参数,默认为private [this]
不包含在任何方法中的代码,就是主构造器的代码。
参考代码:
object Example17 {
class Person(var name:String = "张三",var age:Int = 24){
println("调用了主构造器")
}
def main(args: Array[String]): Unit = {
//空参
val p1 = new Person("李四",2)
//全参
val p2 = new Person()
//指定参数
val p3 = new Person(age = 2)
println(s"p1:${p1.name},${p1.age}")
println(s"p2:${p2.name},${p2.age}")
println(s"p3:${p3.name},${p3.age}")
}
}
2.10.3.2 辅助构造器
辅助构造器方法名默认是this,且不能更改。辅助构造器的第一行代码必须调用主构造器或其他辅助构造器,创建对象的顺序一定是主构造器最先执行。
语法:
def this(参数名:类型,...){
//第一行需要调用主构造器或其他构造器
//构造器代码
}
示例:
object Example18 {
class Person {
println("调用了主构造器")
private var name = ""
private var age = 0
def this(name: String){
this() //调用主构造器
println("调用了辅助构造器-")
this.name = name
}
def this(name: String, age: Int){
this(name) //调用其他的辅助构造器
println("调用了辅助构造器--")
this.age = age
}
}
def main(args: Array[String]): Unit = {
new Person("zs",20)
}
}
2.10.4 单例对象
Scala中是没有static关键字的,想要定义类似于Java中的static变量、static方法,就要使用Scala中的单例对象object。单例对象表示全局仅有一个对象,也叫孤立对象,定义单例对象和定义类很像,就是把class换成object。
它的主要作用:
1)存放工具方法和常量
2)高效共享单个不可变的实例
3)需要单个实例协调某个服务时
格式:
object 单例对象名{}
在object中定义的成员变量类似Java中的静态变量,在内存中只有一个对象。
单例对象中,可以直接使用 单例对象名. 的方式调用成员。
示例:
object Example19 {
object Dog{
val leg_num = 4
def say(): Unit = println("dog")
}
def main(args: Array[String]): Unit = {
println(Dog.leg_num)
Dog.say()
}
}
2.10.5 伴生对象
在Java或C++中,通常会用到既有实例方法又有静态方法的类。在 Scala中,可以通过类和与类同名的伴生对象来达到同样的目的。
定义:
在同一个scala的类文件中,如果class和object的名字相同,那么class是object的伴生类,object是class的伴生对象。
注意:
- 伴生对象和伴生类名字必须相同
- 伴生对象和伴生类必须在同一个Scala源文件中
- 伴生对象和伴生类可以互相访问private属性,如果权限为 private[this] ,只能在当前类访问,伴生对象也不能直接访问。
参考代码:
object Example21 {
// 伴生类
class Person{
def eat(): Unit = print("吃" + Person.food)
}
// 伴生对象
object Person{
private val food = "饭"
}
def main(args: Array[String]): Unit = {
val person = new Person
person.eat()
}
}
2.10.6 继承
语法:
class/object 子类 extends 父类{
}
子类重写方法必须使用 override修饰。
可以使用override重写一个val 字段,父类的var字段不可重写。
示例:
object Example22 {
class Person{
val name = ""
var age = 24
def say(): Unit = print("")
}
class Student extends Person{
override val name = "sjh"
override def say(): Unit = print("hello")
}
def main(args: Array[String]): Unit = {
val student = new Student()
student.say()//hello
}
}
2.10.7 抽象类
格式:
abstract class 抽象类名{
val/var 抽象字段名:类型
def 方法名(参数:参数类型...):返回类型
}
示例:
object Example23 {
abstract class Shape{
val c:Int
def getArea:Double
}
class Square(x:Int) extends Shape{
override val c: Int = x
override def getArea: Double = c * c
}
class Circle(r:Int) extends Shape{
override val c: Int = r
override def getArea: Double = Math.PI * c * c
}
def main(args: Array[String]): Unit = {
val square = new Square(1)
println(square.getArea)//1
val circle = new Circle(1)
println(circle.getArea)//3.141592653589793
}
}
2.10.8 样例类/样例对象
2.10.8.1 样例类
样例类是一种特殊类,一般用于保存数据(类似于Java的 POJO),在并发编程以及Spark、 Flink等框架中会经常使用。
格式:
case class 样例类名([var/val] 成员变量名1:类型1...)
示例:
case class Person(val name:String, var age:Int)
如果不写,变量默认修饰符是val,val是可以省略不写的。
如果要实现某个成员变量值可以被修改,则需要手动添加var 修饰。
自动创建伴生对象,同时在里面给我们实现一些方法,常用的方法如下:
apply()方法
可以快速使用类名创建对象,省略 new 关键字。
toString()方法
可以在打印时直接打印该对象各个属性值。
例如:val p = Person()
equals()方法
可以直接使用 == 直接比较属性值。
hashCode()方法
同一个对象哈希值一定相同,不同对象哈希值一般不同。
copy()方法
可以用来快速创建属性值相同的实例对象,还可以使用带名参数的形式给指定的成员变量赋值。
unapply()方法
实现提取器,应用于模式匹配。
2.10.8.2 样例对象
用case修饰的单例对象就叫样例对象,而且它没有主构造器,主要用在:
- 枚举值
- 作为没有任何参数的消息传递
示例:
object ClassDemo {
//特质Sex,表示性别
trait Sex
//样例对象,表示男,继承 Sex 特质
case object Male extends Sex
//样例对象,表示女,继承 Sex 特质
case object Female extends Sex
//定义样例类 Person
case class Person(var name:String, var sex:Sex){}
def main(args: Array[String]): Unit = {
val p = Person("sjh", Male)
}
}
2.11 特质
如果我们需要在不影响当前继承体系的情况下,对某些类(或者某些对象)的功能进行加强,就需要将这些特有的功能放到特质中。而Scala 中的特质要用关键字 trait 修饰。
特点
特质可以提高代码的复用性。
特质可以提高代码的扩展性和可维护性。
类与特质是继承关系,类与类只支持单继承,类与特质之间可以单继承也可以多继承。
Scala的特质中可以有普通字段、抽象字段、普通方法、抽象方法。
如果特质只有抽象内容,这样的特质叫瘦接口。
如果特质既有抽象内容又有具体内容,这样的特质叫做富接口。
语法:
定义特质
trait 特质名称{
//普通字段
//抽象字段
//普通方法
//抽象方法
}
继承特质
class 类名 extends 特质1 with 特质2{
//重写抽象字段
//重写抽象方法
}
参考代码:
object Example25 {
trait MsgSender{
def send(msg:String)
}
trait MsgReceiver{
def receive()
}
// 类继承多个特质
class MsgWorker extends MsgSender with MsgReceiver{
override def send(msg: String): Unit = println(s"发送消息:$msg")
override def receive(): Unit = println("接收消息")
}
// object 继承 trait
object MsgWorker extends MsgSender with MsgReceiver{
override def send(msg: String): Unit = println(s"发送消息:$msg")
override def receive(): Unit = println("接收消息")
}
def main(args: Array[String]): Unit = {
val worker = new MsgWorker
worker.send("hello")
worker.receive()
MsgWorker.send("hello scala")
MsgWorker.receive()
}
}
2.11.1 对象混入trait
有些时候我们希望在不改变类继承体系的情况下,对对象的功能进行临时增强或者扩展,这个时候就可以考虑使用对象混入技术。所谓的对象混入技术的就是在 scala中,类和特质之间无任何的继承关系,但是通过特定的关键字却可以让该类对象具有指定特质中的成员。
语法:
val/var 对象名 = new 类 with 特质
参考代码:
object Example26 {
trait Logger{
def log(): Unit = println("log..")
}
class User{
}
def main(args: Array[String]): Unit = {
// val/var 对象名 = new 类 with 特质
val user = new User with Logger
user.log()
}
}
2.11.2 trait构造机制
每个特质只有一个无参构造器,也就是说trait也有构造代码,但和类不一样,特质不能有构造器参数。
遇到一个类继承另一个类以及多个trait的情况,创建该类实例时构造器执行顺序:
执行父类构造器
从左到右依次执行 trait 的构造器
如果trait 有父 trait,先执行父 trait的构造器
如果多个 trait 有相同的父 trait,父 trait 构造器只初始化一次
执行子类构造器
参考代码:
object Example27 {
trait A{
println("A")
}
trait B extends A{
println("B")
}
trait C extends A{
println("C")
}
class D{
println("D")
}
class E extends D with B with C{
println("E")
}
def main(args: Array[String]): Unit = {
new E//DABCE
}
}
2.12 包
包就是文件夹,用package修饰,可以区分重名类。
格式:
格式1:文件顶部标记法-合并版
Package 包名1.包名2.包名3
格式2:文件顶部标记法-拆解版
Package 包名1.包名2
Package 包名3
格式3:串联式包语句
Package 包名1.包名2{
Package 包名3{
}
}
包的作用域:
子包可以直接访问父包中的内容。
上层访问下层内容时,可以通过导包(import)或者写全包名的形式实现。
如果上下层有相同的类,使用时采用就近原则(优先使用下层)。
包的引入:
Scala 默认引入了java.lang包的全部内容,scala包以及Predef 包的部分内容。
包的引入不限于Scala文件的顶部,而是可以编写到任何需要使用的地方。
如果需要导入某个包中的所有类和特质,使用下划线 _ 实现。
如果引入的多个包含有相同的类,可以通过重命名或隐藏解决。
重命名格式:
import java.util.{HashSet => JavaSet}
隐藏格式:
import java.util.{HashSet => _,_}//引入util包下除了HashSet的类
2.13 模式匹配
模式匹配是检查某个值(value)是否匹配某一个模式的机制,一个成功的匹配同时会将匹配值解构为其组成部分。它是Java中的switch语句的升级版,同样可以用于替代一系列的 if/else 语句。
一个模式匹配语句包括一个待匹配的值,match关键字,以及至少一个case语句。
match对应Java里的switch,但是写在选择器表达式之后。即: 选择器 match {备选项}。
match表达式通过以代码编写的先后次序尝试每个模式来完成计算,只要发现有一个匹配的case,剩下的case不会继续匹配。
match..case 和 switch..case的区别
Java写法
switch(n) {
case(1): ...; break;
case(2): ...; break;
default: ...;
}
Scala写法 ,每次结束不用写break , _相当于default
n match {
case "a" | "b" => ...
case "c" => ...
case _ => ...
}
2.13.1 简单模式匹配
格式:
变量 match{
case 常量1 => 表达式1
case 常量2 => 表达式2
case 常量3 => 表达式3
case _ => 表达式4 //默认项
}
示例:
def main(args: Array[String]): Unit = {
println("输入一个单词")
val word = StdIn.readLine()
word match {
case "hadoop" => println("大数据分布式存储和计算框架")
case "zookeeper" => println("大数据分布式协调服务框架")
case "spark" => println("大数据分布式内存计算框架")
case _ => println("未匹配")
}
}
2.13.2 类型匹配
除了匹配数据之外,matcha表达式还可以进行类型匹配。如果我们要根据不同的数据类型来执行不同的逻辑,可以使用match表达式来实现。
格式:
对象名 match{
case 变量名1:类型1 => 表达式1
case 变量名2:类型2 => 表达式2
case _:类型3 => 表达式3 //表达式没用到变量名可以使用下划线代替
case _ => 表达式4 //默认项
}
示例:
def main(args: Array[String]): Unit = {
val a:Any = "hadoop"
a match {
case x:String => println(s"${x}是字符串")
case x:Int => println(s"${x}是数字")
case _ => println("未匹配")
}
}
2.13.3 守卫
守卫指在case中添加 if 条件判断,这样可以让代码更简洁。
格式:
变量 match{
case 变量名 if 条件1 => 表达式1
case 变量名 if 条件2 => 表达式2
case 变量名 if 条件3=> 表达式3
case _ => 表达式4 //默认项
}
示例:
def main(args: Array[String]): Unit = {
val a = StdIn.readInt()
a match {
case x if x > 0 => println("正数")
case x if x < 0 => println("负数")
case _ => println("是0")
}
}
2.13.4 样例类匹配
Scala中可以使用模式匹配来匹配样例类,从而实现可以快速获取样例类中的成员数据。
格式:
对象名 match{
case 样例类型1(字段1, 字段2..) => 表达式1
case 样例类型2(字段1, 字段2..) => 表达式2
case 样例类型3(字段1, 字段2..) => 表达式3
case _ => 表达式4 //默认项
}
示例:
object ClassDemo {
case class Customer(name:String, age:Int)
case class Order(id:Int)
def main(args: Array[String]): Unit = {
val a:Any = Customer("sjh", 20)
a match {
case Customer => println("是customer")
case Order => println("是order")
case _ => println("未匹配")
}
}
}
2.13.5 集合匹配
匹配数组示例
def main(args: Array[String]): Unit = {
//定义三个数组
val arr1 = Array(1, 2, 3)
val arr2 = Array(0)
val arr3 = Array(0 , 1, 2, 3,4)
arr1 match {
case Array(1, _, _) => println("长度为3,首元素为1")
case Array(0) => println("长度为1,元素0")
case Array(0, _*) => println("首元素0,其余任意")
}
}
匹配列表示例
def main(args: Array[String]): Unit = {
//定义三个列表
val list1 = List(1, 2, 3)
val list2 = List(0)
val list3 = List(0, 1, 2, 3,4)
list3 match {
case List(1, _, _) => println("长度为3,首元素为1")
case List(0) => println("长度为1,元素0")
case List(0, _*) => println("首元素0,其余任意")
case List(x, y) => println(s"匹配列表:只包含两个任意元素的列表,元素为:${x},${y}")
}
//等价于
list2 match {
case 1 :: _ :: _ ::Nil => println("长度为3,首元素为1")
case 0 :: Nil => println("长度为1,元素0")
case 0 :: _ => println("首元素0,其余任意")
case x :: y :: Nil => println(s"匹配列表:只包含两个任意元素的列表,元素为:${x},${y}")
}
}
匹配元组示例
def main(args: Array[String]): Unit = {
//定义三个元组
val a = (1, 2, 3)
val b = (3, 4, 5)
val c = (3, 4)
a match {
case (1, _, _) => println("长度为3,首元素为1")
case (_, _, 5) => println("长度为3,尾元素为5")
case _ => println("不匹配")
}
}
2.14 高阶函数
Scala 混合了面向对象和函数式特性,如果一个函数的参数列表可以接收函数对象,那么这个函数就被称为高阶函数。
2.14.1 作为值的函数
可以将函数对象传递给方法 。
示例:将一个整数列表中的每个元素转换为对应个数的*。
def main(args: Array[String]): Unit = {
val list = (1 to 5).toList
val func = (x:Int) => "*" * x
val list1 = list.map(func)
println(list1)//List(*, **, ***, ****, *****)
}
2.14.2 匿名函数
没有赋值给变量的函数就是匿名函数
示例:
def main(args: Array[String]): Unit = {
val list = (1 to 5).toList
val list1 = list.map((x:Int) => "*" * x)
val list2 = list.map("*" * _)
println(list1)//List(*, **, ***, ****, *****)
println(list2)//List(*, **, ***, ****, *****)
}
2.14.3 柯里化
柯里化指将原先接收多个参数的方法转换为多个只有一个参数列表的过程
示例:定义方法完成两个字符串的拼接
object ClassDemo {
//普通写法
def merge1(str1:String, str2:String): String = str1 + str2
//柯里化 f1表示函数
def merge2(str1:String, str2:String)(f1:(String, String) => String): String = f1(str1, str2)
def main(args: Array[String]): Unit = {
println(merge1("abc", "def"))//abcdef
println(merge2("abc", "def")(_ + _))//abcdef
}
}
2.14.4 闭包
闭包指 可以访问不在当前作用域范围数据的一个函数。(柯里化就是一个闭包)
示例:通过闭包获取两个整数的和
def main(args: Array[String]): Unit = {
val x = 10
val sum = (y:Int) => x + y
println(sum(10))//20
}
2.14.5 控制抽象
假设函数 A 的参数列表需要接收一个函数 B,而函数B没有输入值也没有返回值,那么函数A称为控制抽象函数。
示例:
object ClassDemo {
val func = (f1:() => Unit) => {
println("welcome")
f1()
println("bye")
}
def main(args: Array[String]): Unit = {
func(() => println("shopping.."))
}
}
2.15 泛型
2.15.1 泛型
泛型的意思是 泛指某种具体的数据类型,在 Scala 中泛型用 [数据类型] 表示。
泛型方法
示例,定义一个泛型方法,获取任意数据类型的中间元素
def getMiddleElement[T](array: Array[T]): T = {array(array.length / 2)}
泛型类
示例,定义一个 Pair 泛型类,包含两个字段且字段类型不固定
class Pair[T](var a:T, var b:T)
泛型特质
trait Logger[T]{
val a:T
def show(b:T)
}
object ConsoleLogger extends Logger[String]{
override val a: String = "sjh"
override def show(b: String): Unit = println(b)
}
2.15.2 上下界
上界
使用 T <: 类型名 表示给类型添加一个上界,表示该类型必须是 T 或 T 的子类。
object ClassDemo {
class Person
class Student extends Person
def demo[T <: Person](array: Array[T]): Unit = println(array)
def main(args: Array[String]): Unit = {
demo(Array(new Person))
demo(Array(new Student))
demo(Array("a"))//报错
}
}
下界
使用 T >: 类型名 表示给类型添加一个下界,表示该类型必须是 T 或 T 的父类。
如果泛型既有上界又有下界,下界写在前面,[T >: A <: B]
2.15.3 协变、逆变、非变
协变:类 A 和 类 B 之间是父子类关系,Pair[A] 和 Pari[B] 之间也有父子关系。
class Pair[+T]{}
逆变:类 A 和 类 B 之间是父子类关系,但 Pair[A] 和 Pari[B] 之间是子父关系。
class Pair[-T]{}
非变:类 A 和 类 B 之间是父子类关系,Pair[A] 和 Pari[B] 之间没有任何关系。
class Pair[T]{} //默认类型是非变的
示例:
object ClassDemo {
class Super
class Sub extends Super
class Temp1[T]
class Temp2[+T]
class Temp3[-T]
def main(args: Array[String]): Unit = {
//测试非变
val t1:Temp1[Sub] = new Temp1[Sub]
//val t2:Temp1[Super] = t1 报错
//测试协变
val t3:Temp2[Sub] = new Temp2[Sub]
val t4:Temp2[Super] = t3
//测试逆变
val t5:Temp3[Super] = new Temp3[Super]
val t6:Temp3[Sub] = t5
}
}
2.16 集合
分类:
可变集合 线程不安全
集合本身可以动态变化,且可变集合提供了改变集合内元素而都方法。
scala.collection.mutable //需要手动导包
不可变集合 线程安全
默认类库,集合内的元素一旦初始化完成就不可再进行更改,任何对集合的改变都将生成一个新的集合。
scala.collection.immutable //不需要手动导包
Set 无序、唯一
·HashSet
·SortedSet
·TreeSet
·BitSet
·ListSet
Seq 序列、有序、可重复、元素有索引
·IndexedSeq 索引序列,随机访问效率高
·NumericRange Range的加强
·Range 有序整数序列,类似等差数列
·Vector 通用不可变的数据结构,获取元素时间长,随机更新快于数组或列表
·String 字符串
·LinearSeq 线性序列,主要操作首尾元素
·List 列表
·Queue 队列
·Stack 栈 不可变栈已经弃用
·Stream 流
Map
·HashMap
·SortedMap
·TreeMap
·ListMap
可变集合比不可变集合更丰富,例如在 Seq 中,增加了 Buffer 集合,例如 ArrayBuffer 和 ListBuffer。
2.16.1 Traverable
Traverable 是一个特质,它的子特质 immutable.Traverable 和 mutable.Traverable 分别是不可变集合和可变集合的父特质。
格式:
创建空的Traverable 对象。
val t = Traverable.empty[Int]
val t = Traverable[Int]()
val t = Nil
创建带参数的Traverable 对象。
val t = List(1, 2, 3).toTraverable
val t = Traverable(1, 2, 3)//默认生成List
转置:
使用 transpose 方法。
转置操作时,需要每个集合的元素个数相同。
def main(args: Array[String]): Unit = {
val t1 = Traversable(Traversable(1, 4, 7), Traversable(2, 5, 8), Traversable(3, 6, 9))
println(t1)//List(List(1, 4, 7), List(2, 5, 8), List(3, 6, 9))
val transpose = t1.transpose
println(transpose)//List(List(1, 2, 3), List(4, 5, 6), List(7, 8, 9))
}
拼接:
++ 可以拼接数据,但是会创建大量临时集合,可以通过 concat 方法实现。
def main(args: Array[String]): Unit = {
val t1 = Traversable(1, 2, 3)
val t2 = Traversable(4, 5, 6)
val traversable = Traversable.concat(t1, t2)
println(traversable)//List(1, 2, 3, 4, 5, 6)
}
筛选:
利用 collect 方法实现偏函数结合集合使用,从集合中筛选指定数据。
def main(args: Array[String]): Unit = {
val t1 = (1 to 10).toList
def filter:PartialFunction[Int, Int] = {
case x if x % 2 == 0 => x
}
//val t2 = t1.collect(filter)
val t2 = t1.collect({
case x if x % 2 == 0 => x
})
println(t2)//List(2, 4, 6, 8, 10)
}
scan 方法
def scan[B](z:B)(op: (B, B) => B)
[B] 表示返回值的数据类型,(z:B)表示初始化值,(op: (B, B) => B)表示具体的函数运算。
示例:定义 traversable 集合,存储1-5,假设初始值为1,分别获取每个元素的阶乘值。
def main(args: Array[String]): Unit = {
val t1 = 1 to 5
//val seq = t1.scan(1)((a, b) => a * b)
val seq = t1.scan(1)(_ * _)
println(seq)//Vector(1, 1, 2, 6, 24, 120)
}
获取集合指定元素
head/last :获取第一个/最后一个元素,不存在抛出异常
headOption/lastOption :获取获取第一个/最后一个元素,返回值是 Option
find:查找符号条件的第一个元素
slice :截取集合中的一部分元素,左闭右开
def main(args: Array[String]): Unit = {
val t1 = 1 to 5
println(t1.head)//1
println(t1.last)//5
println(t1.headOption)//Some(1)
println(t1.lastOption)//Some(5)
println(t1.find(_ % 2 == 0))//Some(2)
println(t1.slice(0, 2))//Vector(1, 2)
}
判断元素是否合法
forall 如果集合中所有元素都满足条件返回 true
exist 如果集合中有一个元素满足条件返回 true
def main(args: Array[String]): Unit = {
val t1 = 1 to 5
println(t1.forall(_ % 2 == 0))//false
println(t1.exists(_ % 2 == 0))//true
}
聚合函数
count 统计集合中满足条件的元素个数
sum 获取集合元素和
product 获取集合中所有元素成绩
max 获取集合中所有元素最大值
min 获取集合中所有元素最小值
集合类型转换
toList
toSet
toArray
toSeq
填充元素
fill 快速生成指定数量的元素
iterator 根据指定条件,生成指定个数的元素
range 生成某个区间内的指定间隔的所有数据,不传间隔参数默认 1
def main(args: Array[String]): Unit = {
val t1 = Traversable.fill(5)("a")
println(t1)//List(a, a, a, a, a)
val t2 = Traversable.fill(2, 2, 2)("a")
println(t2)//List(List(List(a, a), List(a, a)), List(List(a, a), List(a, a)))
val t3 = Traversable.iterate(1, 5)(_ * 10)
println(t3)//List(1, 10, 100, 1000, 10000)
val t4 = Traversable.range(1, 100, 7)
println(t4)//List(1, 8, 15, 22, 29, 36, 43, 50, 57, 64, 71, 78, 85, 92, 99)
}
Iterable
Iterable 表示一个可以迭代的集合,它继承了 Travsersable 特质,同时也是其他集合的父特质。它定义了获取迭代器的方法,这是一个抽象方法。
遍历集合示例:
def main(args: Array[String]): Unit = {
val list = List(1, 2, 3, 4, 5)
//方式一 主动迭代
val iterator = list.iterator
while (iterator.hasNext)
print(iterator.next() + " ")
//方式二 被动迭代
list.foreach(x => print(x + " "))
}
分组遍历示例:
def main(args: Array[String]): Unit = {
val i = (1 to 5).toIterable
val iterator = i.grouped(2)
while (iterator.hasNext)
print(iterator.next() +" ")//Vector(1, 2) Vector(3, 4) Vector(5)
}
按照索引生成元组:
def main(args: Array[String]): Unit = {
val i = Iterable("A", "B", "C", "D", "E")
val tuples = i.zipWithIndex.map(x => x._2 -> x._1)
println(tuples)//List((0,A), (1,B), (2,C), (3,D), (4,E))
}
判断集合是否相同:
def main(args: Array[String]): Unit = {
val i1 = Iterable("A", "B", "C")
val i2 = Iterable("A", "C", "B")
val i3 = Iterable("A", "B", "C")
println(i1.sameElements(i2))//false
println(i1.sameElements(i3))//true
}
2.16.2 Seq
Seq 特质代表 按照一定顺序排列的元素序列,序列是一种特别的可迭代集合,它的特点是有序、可重复、有索引
创建 Seq 集合:
def main(args: Array[String]): Unit = {
val seq = (1 to 5).toSeq
println(seq)//Range(1, 2, 3, 4, 5)
}
获取长度及元素:
通过 length 或 size 方法获取长度,通过索引直接获取元素。
def main(args: Array[String]): Unit = {
val seq = (1 to 5).toSeq
println(seq.length)//5
println(seq.size)//5
println(seq(0))//1
}
获取指定元素的索引值
indexOf 获取指定元素在列表中第一次出现的位置
lastIndexOf 获取指定元素在列表中最后一次出现的位置
indexWhere 获取满足条件的元素,在集合中第一次出现的索引
lastIndexWhere 获取满足条件的元素,在集合中最后一次出现的索引
indexOfSlice 获取指定的子序列在集合中第一次出现的位置
找不到返回 -1。
判断是否包含指定数据:
startsWith 是否以指定子序列开头
endsWith 是否以指定子序列结尾
contains 判断是否包含某个指定数据
containsSlice 判断是否包含某个指定子序列
修改指定的元素:
updated 修改指定索引位置的元素为指定的值。
patch 修改指定区间的元素为指定的值。
def main(args: Array[String]): Unit = {
val seq = (1 to 5).toSeq
val seq1 = seq.updated(0, 5)
println(seq1)//Vector(5, 2, 3, 4, 5)
//参数1 起始索引 参数2 替换后的元素 参数3 替换几个
val seq2 = seq.patch(1, Seq(1, 2), 3)
println(seq2)//Vector(1, 1, 2, 4, 5)
}
2.16.3 Stack
top 获取栈顶元素
push 入栈
pop 出栈
clear 清除所有元素
mutable.Stack 有一个独有方法 pushAll,把多个元素压入栈中。
ArrayStack 有独有方法为:dup,复制栈顶元素。preseving,执行表达式,执行完毕后恢复栈。
示例:
def main(args: Array[String]): Unit = {
//从右往左入栈
val s = mutable.Stack(1, 2, 3, 4, 5)
println(s.top)//1
println(s.push(6))//Stack(6, 1, 2, 3, 4, 5)
println(s.pushAll(Seq(7, 8, 9)))//Stack(9, 8, 7, 6, 1, 2, 3, 4, 5)
println(s.pop())//9
println(s.clear())//()
}
示例:定义可变栈存储1-5,通过dup方法复制栈顶元素,通过preseving 方法先清空元素再恢复。
def main(args: Array[String]): Unit = {
//从右往左入栈
val s = mutable.ArrayStack(1, 2, 3, 4, 5)
//复制栈顶元素
s.dup()
println(s)//ArrayStack(1, 1, 2, 3, 4, 5)
s.preserving({
//该方法执行后,栈中数据会恢复
s.clear()
})
println(s)//ArrayStack(1, 1, 2, 3, 4, 5)
}
2.16.4 Queue
表示队列,特点是 FIFO,常用的队列是 mutable.Queue,内部以 MutableList 实现。
enqueue 入队
dequeue 出队
dequeueAll 移除所有满足条件的元素
dequeueFirst 移除第一个满足条件的元素
3. Actor模型
Actor介绍
Scala中的Actor并发编程模型可以用来开发比Java线程效率更高的并发程序。
Java并发编程的问题
在Java并发编程中,每个对象都有一个逻辑监视器(monitor),可以用来控制对象的多线程访问。我们添加sychronized关键字来标记,需要进行同步加锁访问。通过加锁的机制来确保同一时间只有一个线程访问共享数据。但这种方式存在资源竞争、以及死锁问题,程序越大问题越麻烦。
资源竞争
线程死锁
Actor并发编程模型
Actor并发编程模型,是Scala提供给程序员的一种Java并发编程完全不一样的并发编程模型,是一种基于事件模型的并发机制。Actor并发编程模型是一种不共享数据,依赖消息传通的一种并发编程模式,有效避兔资源争夺、死锁等情况。