Scala 语法之走马观花

摘要

走马观花的学习了一下 Scala 的基本语法,以下示例基于 Scala 2.11.12 版本编写。

变量

可变和不可变量

Scala 中变量有两种不同类型:
1)val:不可变,在声明时必须初始化,且之后不能再复制
2)var:可变,声明时需要初始化,之后可以再次赋值

数据类型

scala 可以根据赋值推断数据类型

1
val myName = "dcwang"

也可以指定类型

1
val myName2 : String = "dcwang"

还可以利用全限定名指定类型,Scala中使用的是Java类型

1
val myName3 : java.lang.String = "dcwang"

所有 scala 文件默认会导入 java.lang 下面所有的包,在 scala中表示为:

1
import java.lang._

用下划线表示所有

1
2
var price = 1.2
var price2 : Double = 2.2

在 scala 中所有的基本类型都是 类

基本运算

在 scala 中所有运算都是调用函数

1
val sum1 = 5 + 3

相当于

1
val sum2 = (5).+(3)

富包装类

Range

1
2
1 to 5
res1: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

1 to 5 相当于 1.to(5)

1
2
scala> 1 until 5
res2: scala.collection.immutable.Range = Range(1, 2, 3, 4)\

1
2
scala> 1 to 10 by 2
res3: scala.collection.immutable.Range = Range(1, 3, 5, 7, 9)

控制结构

判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
val x = 6

if (x > 0) {
// do something
} else {
// do something
}

if (x > 0) {
//
} else if (x == 0) {
//
} else {
//
}

scala 中可以将 if 中判断的值赋值给变量

while 循环

1
2
3
4
5
6
var i = 9

while (i > 0) {
i -= 1
printf("i is %d \n", i)
}

for 循环

1
2
3
4
5
6
7
8
9
10
11
12
for (i <- 1 to 5)
println(i)

for (i <- 1 to 5 by 2)
println(i)

// 守卫(guard)表达式
for (i <- 1 to 5 if i % 2 == 0)
println(i)

for (i <- 1 to 5; j <- 1 to 3)
println(i * j)

文件操作

文本文件读写

Scala 需要调用 java.io.PrintWriter 实现文件写入

1
2
3
4
5
6
7
8
import java.io.PrintWriter

val out = new PrintWriter("output.txt")

for (i <- 1 to 5)
out.println(i)

out.close()

使用 Scala.io.Source 的 getLines 方法实现对文件中所有行的读取

1
2
3
4
5
6
7
8
import scala.io.Source

val inputFile = Source.fromFile("output.txt")

val lines = inputFile.getLines

for (line <- lines)
println(line)

异常捕获

Scala 不支持 Java 中的 checked exception,将所有异常当做运行时异常
Scala 仍然使用 try-catch 捕获异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.FileReader
import java.io.FileNotFoundException
import java.io.IOException

try {
val file = new FileReader("NotExistFile.txt")
} catch {
case ex: FileNotFoundException =>
// do something
case ex: IOException =>
// do something
} finally {
file.close()
}

容器 Collections

Scala 提供了一套丰富的容器库,包括列表、数组、集合、映射等
Scala 用三个包来组织容器,分别是

1
2
3
scala.collection
scala.collection.mntable
scala.collection.immutable

列表 List

共享相同类型的不可变的对象序列,定义在 scala.collection.immutable 中
List 在声明时必须初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var strList = List("BigData", "Hadoop", "Spark")

// 返回头部第一个元素
strList.head

// 返回除了第一个之外的其他元素(一个新的List)
strList.tail

// 连接操作(从右侧开始)
var oneList = List(1, 2, 3)
var otherList = List(4, 5)
var newList = oneList::otherList

// Nil 是一个空列表对象
var intList = 1::2::3::Nil

集合 Set

集合中的元素插入式无序的,以哈希方法对元素的值进行组织,便于快速查找

集和包括可变和不可变集和,分别位于 scala.collection.mntablescala.collection.immutable 包中,默认情况下是不可变集和。

1
2
var mySet = Set("Hadoop", "Spark")
mySet += "Scala"

变量是可变的,集合不可变,加操作导致生成了一个新的集合。

可变集合,在原集合中增加一个元素。

1
2
3
4
import scala.collection.mutable.Set

val myMutableSet = Set("BigData", "Spark")
myMutableSet += "Scala"

映射 Map

映射时一系列键值对的容器。也有可变和不可变两个版本,默认情况下是不可变的。

1
val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University", "PKU" -> "Peking University")

可变映射

1
2
3
4
5
6
import scala.collection.mutable.Map

val university = Map("XMU" -> "Xiamen University", "THU" -> "Tsinghua University", "PKU" -> "Peking University")

university("XMU") = "Ximan University" // update
university("FZU") = "Fuzhou University" // add

遍历映射

1
2
3
4
5
6
7
8
9
10
11
for ( (k,v) <- university) {
// do something
}

for ( k <- university.keys ) {
// do something
}

for ( v <- university.values ) {
// do something
}

迭代器 Iterator

迭代器不是一个集合,而是一种访问集合的方法。

迭代器有两个基本操作:next 和 hasNext。

1
2
3
4
5
6
7
8
9
val iter = Iterator("Hadoop", "Spark", "Scala")

while (iter.hasNext) {
println(iter.next())
}

for (elem <- iter) {
println(elem)
}

grouped & sliding

grouped 返回元素的增量分块

1
2
3
4
5
6
7
8
9
10
11
scala> val xs = List(1,2,3,4,5)
xs: List[Int] = List(1, 2, 3, 4, 5)

scala> val git = xs grouped 3
git: Iterator[List[Int]] = non-empty iterator

scala> git.next()
res0: List[Int] = List(1, 2, 3)

scala> git.next()
res1: List[Int] = List(4, 5)

sliding 生成一个滑动元素的窗口

1
2
3
4
5
6
7
8
9
10
11
scala> val sit = xs sliding 3
sit: Iterator[List[Int]] = non-empty iterator

scala> sit.next()
res3: List[Int] = List(1, 2, 3)

scala> sit.next()
res4: List[Int] = List(2, 3, 4)

scala> sit.next()
res5: List[Int] = List(3, 4, 5)

数组 Array

是一种可变的、可索引的、元素具有相同类型的数据集合,Scala提供了类似于Java中泛型的机制指定数组类型。也可以不指定类型。

1
2
3
4
5
6
val intValueArr = new Array[Int](3)
intValueArr(0) = 23
intValueArr(1) = 34
intValueArr(2) = 45

val strArr = Array("BigData", "Hadoop", "Spark")

多维数组

定义多维数组使用 ofDim() 方法

1
val myMatrix = Array.ofDim[Int](3,4) // 三行四列

访问元素

1
myMatrix(0)(1)

不定长数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import scala.collection.mutable.ArrayBuffer

val aMutableArr = ArrayBuffer(10, 20, 30)
aMutableArr += 40
println(aMutableArr) // ArrayBuffer(10, 20, 30, 40)

aMutableArr.insert(2, 60, 40)
println(aMutableArr) // ArrayBuffer(10, 20, 60, 40, 30, 40)

aMutableArr -= 40
println(aMutableArr) // ArrayBuffer(10, 20, 60, 30, 40)

var temp = aMutableArr.remove(2)
println(aMutableArr) // ArrayBuffer(10, 20, 30, 40)

元组 Tuple

不同类型的值的集合。

1
2
3
val tuple = ("BigData", 2015, 45.0)
println(tuple._1)
println(tuple._2)

面向对象

基本类结构

简单类

1
2
3
4
5
6
7
8
9
class Counter {
private var value = 0

def increment(): Unit = {
value += 1
}

def current(): Int = { value }
}

定义方法可以省略返回类型:

1
2
3
4
5
6
7
8
9
class Counter {
private var value = 0

def increment() {
value += 1
}

def current(): Int = { value }
}

方法传参

1
2
3
4
5
6
7
8
9
class Counter {
private var value = 0

def increment(step: Int) {
value += step
}

def current(): Int = { value }
}

创建对象

1
2
3
4
5
val myCounter = new Counter
myCounter.increment()
println(myCounter.current())

val myCounter2 = new Counter()

编译&执行

如下定义的这样一个类在执行时,直接使用 scala 解释器执行即可,不需要编译。

1
2
3
4
5
6
7
8
9
class Counter {
private var value = 0

def increment() {
value += 1
}

def current(): Int = { value }
}

如果要编译,需要创建一个单例对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Counter {
private var value = 0

def increment() {
value += 1
}

def current(): Int = { value }
}

object MyCounter {
def main(args:Array[String]) {
val myCounter = new Counter
myCounter.increment()
println(myCounter.current)
}
}

编译(后面跟的是文件名):

1
scalac counter.scala

编译之后会产生一些文件:

1
2
3
Counter.class
MyCounter.class
MyCounter$.class

执行
执行时后面跟的是包含 main 方法的对象名称

1
scala -classpath . MyCounter

getter & setter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Person {
private var privateName = "dcwang"
private var privateAge = 25

//
def name = privateName

def setName(newName : String) {
privateName = newName
}

def age = privateAge

def setAge(newAge : Int) {
privateAge = newAge
}

def grown(step : Int) {
privateAge += step
}
}

object SomeOne {
def main(args:Array[String]) {
val someOne = new Person
println(someOne.name)

someOne.setName("yz")
println(someOne.name)

println(someOne.age)
someOne.grown(2)
println(someOne.age)
}
}

构造器

Scala构造器包含一个主构造器和若干个辅构造器
辅助构造器的名称为 this ,每个辅助构造器都必须调用一个已有的构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Person {
private var privateName = "dcwang"
private var privateAge = 25

def this(name : String) {
this() // invoke main constructor
this.privateName = name
}

def this(name : String, age : Int) {
this(name)
this.privateAge = age
}

def name = privateName

def setName(newName : String) {
privateName = newName
}

def age = privateAge

def setAge(newAge : Int) {
privateAge = newAge
}

def grown(step : Int) {
privateAge += step
}
}

object SomeOne {
def main(args:Array[String]) {
val someOne = new Person("yz", 18)
println(someOne.name)
println(someOne.age)
}
}

对象

单例对象

1
2
3
4
5
6
7
8
9
10
11
12
object Person {
private var id = 0

def newId() = {
id += 1
id
}
}

println(Person.newId())
println(Person.newId())
println(Person.newId())

伴生对象

在 Java 中经常用到同时包含实例方法和静态方法的类,在 Scala 中可以用伴生对象来实现
类和它的伴生对象必须在同一个文件中,并且可以相互访问私有成员
当单例对象与某个类具有相同的名称时,它被成为这个类的伴生对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Person {
private val id = Person.newPersonId()
private var name = ""

def this(name : String) {
this()
this.name = name
}

def info() { println("The id of %s is %d. \n".format(name, id)) }
}

object Person {
private var lastId = 0

private def newPersonId() = {
lastId += 1
lastId
}

def main(args : Array[String]) {
val person1 = new Person("yz")
val person2 = new Person("yj")

person1.info()
person2.info()
}
}

applay 方法

update 方法

继承

在子类中重写超类抽象方法时不需要使用 override 关键字
重写一个非抽象方法必须使用 override 修饰符
只有主构造器可以调用超类的主构造器
可以重写超类中的子段

抽象类

1
2
3
4
5
6
7
8
// 抽象类,不能直接实例化
abstract class Car {
// 抽象子段,不需要初始化
val carBrand : String
// 抽象方法,不需要使用 abstract 关键字
def info()
def greeting() { println("Welcome to my car!") }
}

继承抽象类

1
2
3
4
5
6
7
8
9
10
11
12
class BMWCar extends Car {
override val carBrand = "BMW"
// 重写抽象方法不用加 override
def info() {
printf("This is a car")
}

// 重写非抽象方法必须使用 override
override def greeting() {
printf("something")
}
}

特质(trait)

在 Scala 中没有接口的概念,而是提供了 trait,它实现了接口的功能,以及许多其他特性
trait 是 Scala 中代码重用的基本单元,可以同时拥有抽象方法和具体方法
在 Scala 中一个类只能继承一个超类,但是可以实现多个 trait,从而拥有 trait 中的方法和字段,实现多重继承。

1
2
3
4
5
6
7
8
9
10
11
12
trait CarId {
var id : Int
def currentId() : Int
}

class BYDCarId extends CarId { // 使用 extends 关键字继承 trait
override var id = 10000
def currentId() : Int = {
id += 1;
id
}
}

混入多个trait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
trait CarId {
var id : Int
def currentId() : Int
}

trait CarGreeting {
def greeting(msg : String) { println(msg) }
}

// 使用 extends 关键字继承 trait
// 后面混入的多个 trait 可以反复使用 with 关键字
class BYDCarId extends CarId with CarGreeting {
override var id = 10000
def currentId() : Int = {
id += 1;
id
}
}

模式匹配

简单匹配
类似于 Java 中的 switch

1
2
3
4
5
6
7
8
9
10
val colorNum = 1

val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case 3 => "yellow"
case _ => "Not Allowed"
}

println(colorStr)

获取匹配值
可以声明一个变量 unexpected,用于获取进行匹配的值,然后在分支中进行操作。

1
2
3
4
5
6
7
8
9
10
val colorNum = 4

val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case 3 => "yellow"
case unexpected => unexpected + " is Not Allowed"
}

println(colorStr)

类型模式
可以匹配元素的类型,根据类型选择不同操作。

1
2
3
4
5
6
7
8
9
10
11
for (elem <- List(9, 12.3, "Spark", "Hello")) {
val str = elem match {
case i : Int => i + " is an int value."
case d : Double => d + " is a double value."
case s : String => s + " is a string value."
case "Hello" => "Hello is here."
case _ => "unexpected value"
}

println(str)
}

守卫(guard)语句
将 case 的选项设置为全匹配,然后用 if 判断进行处理。那为什么不直接用判断?

case类的匹配
一次匹配多个值,或者对比对象?

1
2
3
4
5
6
7
8
9
10
11
12
13
case class Car(brand: String, price: Int)

val myBYDCar = new Car("BYD", 89000)
val myBMWCar = new Car("BMW", 1200000)
val myBenzCar = new Car("Benz", 1500000)

for (car <- List(myBYDCar, myBMWCar, myBenzCar)) {
car match {
case Car("BYD", 89000) => println("BYD")
case Car("BMW", 1200000) => println("BMW")
case Car(brand, price) => println(brand + price)
}
}

Option类型
处理 None 返回值

1
2
3
4
5
6
7
val someMap = Map("spark" -> 123, "hadoop" -> 234)

var someValue = someMap.get("hive")
println(someValue.getOrElse("No such value")) // No such value

someValue = someMap.get("spark")
println(someValue.getOrElse("No such value")) // 123

Option[T] 类中的 T 可以是各种数据类型,如果一个 Option 对象中包含值,那么这个对象就是 Some 类型,否则就是 None 类型。

如果返回值是一个集合,就可以对其使用 mapforeachfilter 等方法

1
someMap.get("spark").foreach(println)

函数式编程

函数定义

函数字面量
每个函数本身是一个值,可以被传递,类似于 JavaScript 中函数的概念。

函数的类型和值
函数的类型是指传入参数和返回值的类型。

1
def counter(value: Int): Int = { value += 1 }

上面这个函数的类型就是: (Int) => Int
如果有多个参数,使用逗号隔开;否则括号可以省略。

函数的值是指去掉了参数类型和返回值类型之后剩下的参数和函数体:

1
(value) => { value += 1 }

对比声明一个基本类型来声明一个函数:

1
val num : Int = 5

基本类型,声明一个变量 num,指定变量类型为 Int,然后给变量赋值为 5

1
2
var counter = (value : Int) => { value + 1 } : Int
println(counter(2))

匿名函数、Lamda表达式与闭包

Lamda 表达式

1
2
3
(参数) => 表达式

(num: Int) => num * 2

闭包
在一个函数内部可以访问外部变量的形式。

1
2
3
4
5
6
var more = 1
var addMore = (x: Int) => x + more
println(addMore(10))

more = 5
println(addMore(10))

占位符语法

为了让函数字面量更简洁,可以使用下划线作为一个或多个参数的占位符,每个参数仅可以在函数字面量中出现一次。

1
2
3
4
5
6
7
val numList = List(1, 2, 3, 4, 5)

val res = numList.filter(x => x > 3)
print(res)

val res2 = numList.filter(_ > 3)
print(res2)

针对集合的操作

map

map操作是针对集合的典型变换操作,将函数应用到集合中的每一个元素上,并产生一个新的结果集合。

1
2
3
4
5
val books = List("Hadoop", "Hive", "Spark")
println(books) // List(Hadoop, Hive, Spark)

val newbooks = books.map(s => s.toUpperCase)
println(newbooks) // List(HADOOP, HIVE, SPARK)

flatMap

调用一个函数,将一个集合中的每个元素处理后形成的多个集合,合并成一个集合。

1
2
3
val books = List("Hadoop", "Hive", "Spark")
val letters = books.flatMap(s => s.toList)
println(letters) // List(H, a, d, o, o, p, H, i, v, e, S, p, a, r, k)

filter

遍历一个集合,过滤其中的元素形成一个新的集合。

1
2
3
4
val books = List("Hadoop", "Hive", "Spark")

val newBooks = books.filter({value => value.contains("a")})
println(newBooks) // List(Hadoop, Spark)

reduce

对给定集合中的两两元素,指定某给定函数的操作。

1
2
3
4
5
6
7
val numList = List(1, 3, 5)
var res = numList.reduceLeft({_ - _})
println(res) // -7

res = numList.reduceRight({_ - _})
println(res) // 3
// 1 - (3 - 5)

fold

带初始值的规约

1
2
val numList = List(1, 3, 5)
val res = numList.fold(10)({_ * _})