Scala
1.Scala概述
官方网站:https://www.scala-lang.org
Scala是一门主要以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程语言的最佳特性综合在一起的编程语言。你可以使用Scala编写出更加精简的程序,同时充分利用并发的威力。由于Scala默认运行于JVM之上,因此 它可以访问任何Java类库并且与Java框架进行互操作,比如Scala可以被编译成JavaScript代码,让我们更便捷、高效地开发Web应用
预备环境
- java jdk
2.Scala基础
2.1 变量与常量
学习Scala的最简单方法就是使用Scala解释器
scala中的变量,通过var
来标准
1 | var age = 18 |
scala中的常量,通过val
来标准
1 | val userName = "MAX" |
创建并指定变量数据类型
1 | var age:Int = 19 |
2.2 基本数据类型
1 | object BasicDataType { |
注:
- 除了String属于java.lang包之外,其他所有的基本类型都是包scala的成员
- Unit表示无值,和其他语言中void等同
- Null表示null 或空引用
- Nothing表示Scala的类层级的最低端,它是任何其他类型的子类型
- AnyRef是Scala里所有引用类(reference class)的基类
- Any是所有其他类的超类
数据类型转换
1 | object DataTypeConvert { |
多行原始字符串
1 | val mutiStr = |
2.3 操作符
Scala为基本类型提供了丰富的操作符集,这些操作符实际只是普通方法调用的另一种表现形式
1 | object Operation { |
Scala里的操作符不是特殊的语法,任何方法都可以是操作符。到底是方法还是操作符取决于你如何使用
操作符分类
前缀操作符(操作数在操作符右侧)
中缀操作符(两个操作数,分别在操作符左右两侧)
后缀操作符(操作数在操作符左侧)
1 | println(1 max 2) |
对象相等性
比较两个对象是否相等,可以使用==,反之只有!=
该操作对所有对象都起作用,而不仅仅是基本类型
2.4 条件判断
单行控制语句
```scala
val fileName = “flume-ng.conf”
val result = if(fileName.endsWith(“.conf”)) true else falseprintln(result)
1
2
3
4
5
6
7
8
9
10
11
12
13
- 多行控制语句
- ```scala
val fileName = "flume-ng.conf"
var result = ""
if(fileName.endsWith(".conf")) {
result = "flume-ng.conf"
}else {
result = "default.conf"
}
println(result)
2.5 函数式风格
指令式风格 vs 函数式风格
- 如果代码包含了任何var变量,那它就是指令式风格
- 如果仅仅包含val,那它或许是函数式的风格
例如
1 | val fileName = "flume-ng.conf" |
2.6 循环
while循环
1
2
3
4
5
6var counter = 0
while(counter < 10) {
counter += 1
}
println("counter: " + counter)题目
实现价值一个亿的人工智能代码
do…while循环
1
2
3
4
5var line = ""
do {
line = readLine("pls input your name:\n")
println("line: " + line)
}while(line != "")for循环
单行
1
2
3val arr = Array(1, 2, 3, 4, 5, 6)
//loop
for(ele <- arr) println("element: " + ele)多行
1
2
3
4
5val arr = Array(1, 2, 3, 4, 5, 6)
//loop
for(ele <- arr) {
println("element: " + ele)
}函数式
1
2
3val arr = Array(1, 2, 3, 4, 5, 6)
//loop
arr.foreach(ele => println("element: " + ele))range
单行
1
for(x <- 0 to 10) println("x: " + x)
多行
1
2
3for(x <- 0 to 10) {
println("x: " + x)
}filter
1
for(x <- 0 to 10 if x % 2 == 0) println("x: " + x)
yield
1
2val newArr = for(x <- 0 to 10 if x % 2 == 0) yield x * 2
println(newArr)lazy
1
2
3
4
5lazy val file = scala.io.Source.fromFile("/Users/peidonggao/Desktop/test.txt")
//loading file
println("loading file...")
//read file data
println(file.mkString)注:scala中移除了break与continue
题目
使用scala完成递归算法,计算某个磁盘下的总文件数
2.7 匹配表达式
scala中的match表达式类似于java语言中的swtich…case语句
1 | val sex = "men" |
2.8 函数
函数的定义
Unit函数
1 | def processLine(fileName: String, width: Int, line: String): Unit = { |
单行函数
1 | object SingleLineFunction { |
头等函数
scala里的函数是一个“头等函数”(first-class value)。像其他的值,函数可以被当成参数传递,也可以被当成结果返回
1 | def main(args: Array[String]): Unit = { |
多行语句头等函数
1 | def main(args: Array[String]): Unit = { |
占位符
如果想让头等函数更加简洁,可以把下划线当做一个或更多参数的占位符
只要每个参数在头等函数内只出现一次
1 | def main(args: Array[String]): Unit = { |
变长参数
1 | def main(args: Array[String]): Unit = { |
3.类与对象
3.1 定义与实例化
类与创建对象的模板,定义了类,就可以通过new
关键字进行类对象的实例化操作
定义类
1 | class User { |
对象实例化
1 | object UserTest { |
3.2 主构造器与辅助构造器
辅助构造器
1 | class User { |
主构造器
1 | class User(newId: Int, newName: String) { |
注:每一个辅助构造器都必须把主构造器的参数列表调用一遍
先决条件
1 | class User(newId: Int, newName: String) { |
3.3 继承与特质
抽象类
1 | abstract class Father(newName: String) { |
子类
1 | class Son(newName: String) extends Father(newName) { |
注:在Scala的构造器中,你不能调用super(params),不像Java,可以用这种方式调用超类构造器
特征
Scala Trait(特征)相当于 Java 的接口,实际上它比接口还功能强大
与接口不同的是,它还可以定义属性和方法的实现
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait
trait定义
1 | trait Human { |
1 | trait Employee { |
实现类
1 | class People extends Human with Employee { |
3.4 伴生对象
在Java或C++中,通常会用到既有实例方法又有静态方法的类
在Scala中,可以通过类和与类同名的伴生对象来达到同样的目的
类和它的伴生对象可以相互访问私有特性,但必须存在同一个源文件中
1 | class Account { |
注:类的伴生对象的功能特性并不在类的作用域内,必须通过对象名.属性或对象名.方法来调用
3.5 应用程序对象
每个Scala程序都必须从一个对象的main方法启动,除了使用main方法,还可以通过继承App程序来启动程序
1 | object HelloWorld extends App { |
4.包和引入
4.1 包
scala中的包有两种定义方式
第一种
1 | package com { |
第二种
1 | package com.web.domain |
可见性
scala中的访问修饰符与java中的相同,不同的是scala可以明确定义类成员的访问范围
1 | package com.web.domain |
4.2 引入
import
语句用于导入其他包中的成员(类,特质,函数等)
使用相同包的成员不需要 import
语句。 导入语句可以有选择性
1 | import users._ // 导入包 users 中的所有成员 |
Scala 不同于 Java 的一点是 Scala 可以在任何地方使用导入
1 | def sqrtplus1(x: Int) = { |
5.集合操作
5.1 数组
定长数组
1
2
3
4
5
6
7//数组初始化
val arr = new Array[Int](10)
//添加元素
arr(0) = 100
arr(1) = 200
arr.foreach(println)创建并初始化
1
2val arr = Array[Int](100, 200)
println(arr.mkString(","))注
Array中的元素访问使用()而不是[]
Scala中的Array以Java数组方式实现,例如java.lang.String[],java.lang.int[]
变长数组
1
2
3
4
5
6
7
8
9
10
11
12
13object ArrayBufferTest {
def main(args: Array[String]): Unit = {
import scala.collection.mutable.ArrayBuffer
var arr = new ArrayBuffer[Int]()
//添加元素
arr += 1
arr += 2
arr += 3
println(arr.mkString(","))
}
}常用算术操作
1
2
3
4
5
6
7
8
9
10
11
12def main(args: Array[String]): Unit = {
import scala.collection.mutable.ArrayBuffer
val arr = ArrayBuffer[Int](1, 2, 3, 4, 5, 6, 7, 8)
//求和
println("sum: " + arr.sum)
//求极值
println("max: " + arr.max)
println("min: " + arr.min)
//简单排序
var sortedArr = arr.sorted
sortedArr.foreach(println)
}
5.2 映射
1 | def main(args: Array[String]): Unit = { |
5.3 元组
1 | object TupleTest { |
6.高级特性
6.1 样例类
定义样例类
一个最简单的案例类定义由关键字case class
,类名,参数列表(可为空)组成
1 | object CaseClassTest { |
注意在实例化案例类Person
时,并没有使用关键字new
,这是因为案例类有一个默认的apply
方法来负责对象的创建
当你创建包含参数的案例类时,这些参数是公开(public)的val
6.2 泛型
定义泛型类
泛型类使用方括号 []
来接受类型参数。一个惯例是使用字母 A
作为参数标识符,当然你可以使用任何参数名称
1 | class Stack[A] { |
使用
1 | val stack = new Stack[Int] |
KV类型
1 | class Pair[K, V](keys: K, values: V) { |
泛型函数
1 | def getMiddle[T](arr: Array[T]): Unit = { |
6.3 高阶函数
高阶函数是指使用其他函数作为参数、或者返回一个函数作为结果的函数
map函数
map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素,并产生一个结果集合
1 | val salaries = Seq(20000, 70000, 40000) |
简化代码
1 | val salaries = Seq(20000, 70000, 40000) |
上述示例中x
没有被显式声明为Int类型,这是因为编译器能够根据map函数期望的类型推断出x
的类型
对于上述代码,一种更惯用的写法为
1 | val salaries = Seq(20000, 70000, 40000) |
flatMap函数
flatMap是map的一种扩展。在flatMap中,我们会传入一个函数,该函数对每个输入都会返回一个集合(而不是一个元素),然后,flatMap把生成的多个集合“拍扁”成为一个集合
1 | val words = Seq("H e l l o", "W r o l d") |
reduce函数
reduce函数可以对集合当中的元素进行归约操作,其中下划线是占位符,两个下划线表示归约的规则是使用前后两个元素,中间的加号,用来表示对元素的操作,这里我们使用的是加法
如果我们不指定reduce是left还是right默认情况下会使用reduceLeft执行操作
1 | val list = List(1, 2, 3, 4, 5, 6) |
foreach函数
foreach函数与map函数相似,都是用于遍历集合对象,并对每一项执行指定的方法。但两者的差异在于:foreach无返回值(准确来说是void),map返回集合对象
1 | val list = List(1, 2, 3, 4, 5, 6) |
groupBy函数
groupBy函数将列表进行分组,分组的依据是应用groupBy函数中函数的返回值
1 | val list = List("a", "b", "c", "d") |
1 | val seq = Seq((100, "Mike"), (85, "Bob"), (90,"Mike")) |
filter函数
filter函数用于保留列表中符合条件的列表元素
1 | val list = List(100, 80, 60, 95, 88) |
count函数
count函数计算列表中所有满足条件的元素的个数
1 | val list = List(100, 80, 60, 95, 88) |
sortBy函数
sortBy函数用于通过它的类型对一个属性或多个属性进行排序
1 | val list = List(100, 80, 60, 95, 88) |
diff函数
diff函数用于保存列表中那些不在另外一个列表中的元素,即从集合中减去与另外一个集合的交集
1 | val list1 = List(1, 2, 3, 4, 5, 6) |
union函数
union函数用于保存两个集合的元素
1 | val list1 = List(1, 3, 5, 6) |
intersect函数
intersect函数用于保存与另外一个集合的交集
1 | val list1 = List(1, 3, 5, 6) |
distinct函数
distinct函数用于保留列表中非重复的元素,相同的元素只会被保留一次
1 | val list = List(1,3,5,7,9,7,9) |
take函数
take函数用于提取列表的前n个元素
1 | val list = List(1,3,5,7,9,7,9) |
drop函数
drop函数用于丢弃前n个元素,返回剩下的元素
1 | val list = List(1,3,5,7,9,7,9) |
partition函数
partition函数用于将列表分为两部分,第一部分为满足条件的元素,第二部分为不满足条件的元素
1 | val list = List(1, 2, 3, 4, 5, 6, 7, 8, 9) |
6.4 型变
定义类
1 | class Animal {} |
1 | class Bird extends Animal {} |
1 | //不变 |
不变
1
2
3
4val cov = new Covariant[Bird](new Bird)
val cov2: Covariant[Animal] = cov
println(cov2)cov不能赋值给cov2,因为Covariant定义成不变类型
协变
B是A的子类,A是B的父类
当我们定义一个协变类型List[+A]时,List[Child]可以是List[Parent]的子类型
修改Covariant类
1
class Covariant[+T](t:T){}
1
2
3
4val cov = new Covariant[Bird](new Bird)
val cov2: Covariant[Animal] = cov
println(cov2)因为Covariant定义成协变类型的,所以Covariant[Bird]是Covariant[Animal]的子类型,所以它可以被赋值给cov2
逆变
修改Covariant类
1
class Covariant[-T](t:T){}
1
2
3
4val cov = new Covariant[Animal](new Animal)
val cov2: Covariant[Bird] = cov
println(cov2)这里Contravariant[-T]定义成逆变类型,所以Contravariant[Animal]被看作Contravariant[Bird]的子类型,故cov可以被赋值给cov2
6.5 隐式转换
代码演示说明
1 | val num: Int = 12.5 //改行代码会提示错误,高精度无法自动转换为低精度类型 |
加入隐式转换
1 | implicit def f1(x: Double): Int = { |
隐式转换函数
隐式转换函数是以implicit
关键字声明的带有单个参数的函数,这种函数将会在某个时刻下自动运行,将某个值从一种类型转换为另一种类型
注
- 隐式转换函数名可以是任意的
- 隐式转换函数与函数名无关,只有函数签名(函数参数类型和返回值类型)有关
- 隐式函数可以有多个,但要保证在当前作用域,只有一个隐式函数被识别
动态方法添加
定义类
1 | class People { |
1 | class Employee { |
1 | object EmployeeTest { |
隐式值
隐式值又叫隐式变量,即由implicit
修饰的变量,编译器会在方法参数缺省的情况下搜索同一作用域的隐式值作为缺省参数
1 | class ImplicitValue { |
7.Actor并发编程
scala中的并发编程思想与Java中的并发编程思想完全不一样
scala中的Actor是一种不共享数据,依赖于消息传递的一种并发编程模式,避免了死锁、资源争夺的情况
scala中的Actor会不断循环自己的邮箱,并通过receive偏函数进行消息的模式匹配并进行相应的处理
如果Actor A与Actor B需要相互通信,首先A要给B发送一个消息,B会有一个收件箱,然后B会不断循环
自己的收件箱,若看见A发送的消息,B就会解析A的消息并执行,处理完后将结果以邮件的方式发送给A
7.1 简单实现
创建Actor
1 | import scala.actors.Actor |
启动Actor
1 | object MessageActorTest { |
7.2 消息发送
消息类定义
1 | import scala.actors.Actor |
消息发送
1 | object HelloActorTest { |
定义样例类
1 | object CaseClasses { |
定义服务端
1 | import CaseClasses.Message |
定义客户端
1 | import CaseClasses.Message |
运行测试
1 | object ClientServerTest { |