Scala编程基础

时间:2022-03-25 03:33:22

Scala与Java的关系... 4

安装Scala. 4

Scala解释器的使用... 4

声明变量... 5

数据类型与操作符... 5

函数调用与apply()函数... 5

if表达式... 6

语句终结符、块表达式... 6

输入和输出... 6

循环... 7

高级for循环... 7

函数的定义与调用... 8

在代码块中定义包含多行语句的函数体... 8

递归函数与返回类型... 8

默认参数... 8

函数调用时带名参数... 9

变长参数... 9

序列作为变长参数... 9

过程... 9

lazy值... 9

异常... 10

Array. 10

ArrayBuffer. 11

遍历Array和ArrayBuffer. 11

数组常见操作... 11

使用yield和函数式编程转换数组... 12

算法案例:移除第一个负数之后的所有负数... 12

算法案例:移除第一个负数之后的所有负数(改良版)... 13

创建Map. 13

访问Map的元素... 13

修改Map的元素... 13

遍历Map. 14

SortedMap和LinkedHashMap. 14

Map的元素类型—Tuple. 14

定义一个简单的类... 14

getter与setter. 15

自定义getter与setter. 15

仅暴露field的getter方法... 15

private[this]的使用... 16

Java风格的getter和setter方法... 16

辅助constructor. 16

主constructor. 17

内部类... 18

object. 18

伴生对象... 19

让object继承抽象类... 19

apply方法... 19

main方法... 20

用object来实现枚举功能... 20

extends. 21

override和super. 21

override field. 21

val、var override/实现 def. 23

isInstanceOf和asInstanceOf. 24

getClass和classOf. 25

使用模式匹配进行类型判断... 25

protected. 25

调用父类的constructor. 26

匿名内部类... 26

抽象类... 27

抽象field. 27

将trait作为接口使用... 28

在Trait中定义具体方法... 28

在Trait中定义具体字段... 29

在Trait中定义抽象字段... 29

为实例混入trait. 29

trait调用链... 30

在trait中覆盖抽象方法... 30

混合使用trait的具体方法和抽象方法... 31

trait的构造机制... 31

trait field的初始化... 32

trait继承class. 33

将函数赋值给变量... 34

匿名函数... 34

高阶函数... 34

高阶函数的类型推断... 35

Scala的常用高阶函数... 35

闭包... 36

SAM转换... 36

Currying函数... 36

return到外层函数... 37

Scala的集合体系结构... 37

List. 37

LinkedList. 38

Set. 38

集合的函数式编程... 38

函数式编程综合案例:统计多个文本内的单词总数... 39

模式匹配... 39

在模式匹配中使用if守卫... 40

在模式匹配中进行变量赋值... 40

对类型进行模式匹配... 40

对Array和List进行模式匹配... 41

case class与模式匹配... 41

Option与模式匹配... 42

类型参数... 42

泛型类... 42

泛型函数... 43

上边界Bounds. 43

下边界Bounds. 44

View Bounds. 45

Context Bounds. 46

Manifest Context
Bounds. 46

协变和逆变... 47

Existential Type. 47

隐式转换... 48

使用隐式转换加强现有类型... 49

隐式转换函数作用域与导入... 49

隐式转换的发生时机... 49

隐式参数... 50

Actor. 50

Actor的创建、启动和消息收发... 50

收发case class类型的消息... 51

Actor之间互相收发消息... 51

同步消息和Future. 52

   

Scala与Java的关系

Scala与Java的关系是非常紧密的!!

因为Scala是基于Java虚拟机,也就是JVM的一门编程语言。所有Scala的代码,都需要经过编译为字节码,然后交由Java虚拟机来运行。

所以Scala和Java是可以无缝互操作的。Scala可以任意调用Java的代码。所以Scala与Java的关系是非常非常紧密的。

安装Scala

·从Scala官方网站下载,http://www.scala-lang.org/download/,windows版本的安装包是scala-2.11.7.msi。

·使用下载下来的安装包安装Scala。

·在PATH环境变量中,配置$SCALA_HOME/bin目录。

·在windows命令行内即可直接键入scala,打开scala命令行,进行scala编程。

Scala解释器的使用

·REPLRead(取值)-> Evaluation(求值)-> Print(打印)-> Loop(循环)。scala解释器也被称为REPL,会快速编译scala代码为字节码,然后交给JVM来执行。

·计算表达式:在scala>命令行内,键入scala代码,解释器会直接返回结果给你。如果你没有指定变量来存放这个值,那么值默认的名称为res,而且会显示结果的数据类型,比如Int、Double、String等等。

·例如,输入1 + 1,会看到res0: Int = 2

·内置变量:在后面可以继续使用res这个变量,以及它存放的值。

·例如,2.0 * res0,返回res1: Double = 4.0

·例如,"Hi,
" + res0,返回res2: String =
Hi, 2

·自动补全:在scala>命令行内,可以使用Tab键进行自动补全。

·例如,输入res2.to,敲击Tab键,解释器会显示出以下选项,toCharArray,toLowerCase,toString,toUpperCase。因为此时无法判定你需要补全的是哪一个,因此会提供给你所有的选项。

·例如,输入res2.toU,敲击Tab键,直接会给你补全为res2.toUpperCase。

声明变量

·声明val变量:可以声明val变量来存放表达式的计算结果。

·例如,val result = 1
+ 1

·后续这些常量是可以继续使用的,例如,2 * result

·但是常量声明后,是无法改变它的值的,例如,result = 1,会返回error: reassignment to val的错误信息。

·声明var变量:如果要声明值可以改变的引用,可以使用var变量。

·例如,val myresult =
1,myresult = 2

·但是在scala程序中,通常建议使用val,也就是常量,因此比如类似于spark的大型复杂系统中,需要大量的网络传输数据,如果使用var,可能会担心值被错误的更改。

·在Java的大型复杂系统的设计和开发中,也使用了类似的特性,我们通常会将传递给其他模块 / 组件 / 服务的对象,设计成不可变类(Immutable Class)。在里面也会使用java的常量定义,比如final,阻止变量的值被改变。从而提高系统的健壮性(robust,鲁棒性),和安全性。

·指定类型:无论声明val变量,还是声明var变量,都可以手动指定其类型,如果不指定的话,scala会自动根据值,进行类型的推断。

·例如,val name:
String = null

·例如,val name: Any =
"leo"

·声明多个变量:可以将多个变量放在一起进行声明。

·例如,val name1,
name2:String = null

·例如,val num1, num2
= 100

数据类型与操作符

·基本数据类型:Byte、Char、Short、Int、Long、Float、Double、Boolean。

·乍一看与Java的基本数据类型的包装类型相同,但是scala没有基本数据类型与包装类型的概念,统一都是类。scala自己会负责基本数据类型和引用类型的转换操作。

·使用以上类型,直接就可以调用大量的函数,例如,1.toString(),1.to(10)。

·类型的加强版类型:scala使用很多加强类给数据类型增加了上百种增强的功能或函数。

·例如,String类通过StringOps类增强了大量的函数,"Hello".intersect(" World")。

·例如,Scala还提供了RichInt、RichDouble、RichChar等类型,RichInt就提供了to函数,1.to(10),此处Int先隐式转换为RichInt,然后再调用其to函数

·基本操作符:scala的算术操作符与java的算术操作符也没有什么区别,比如+、-、*、/、%等,以及&、|、^、>>、<<等。

·但是,在scala中,这些操作符其实是数据类型的函数,比如1 + 1,可以写做1.+(1)

·例如,1.to(10),又可以写做1 to 10

·scala中没有提供++、--操作符,我们只能使用+和-,比如counter = 1,counter++是错误的,必须写做counter += 1.

函数调用与apply()函数

·函数调用方式:在scala中,函数调用也很简单。

·例如,import
scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。

·不同的一点是,如果调用函数时,不需要传递参数,则scala允许调用函数时省略括号的,例如,"Hello
World".distinct

·apply函数

    ·Scala中的apply函数是非常特殊的一种函数,在Scala的object中,可以声明apply函数。而使用“类名()”(严格来讲应该是“对象名()”)的形式,其实就是“类名.apply()”(严格来讲应该是“对象名.apply()”)的一种缩写。通常使用这种方式来构造类的对象,而不是使用“new 类名()”的方式。

·例如,"Hello
World"(6),因为在StringOps类中有def apply(n: Int): Char的函数定义,所以"Hello World"(6),实际上是"Hello
World".apply(6)的缩写。

·例如,Array(1, 2, 3,
4),实际上是用Array object的apply()函数来创建Array类的实例,也就是一个数组。

if表达式

·if表达式的定义:在Scala中,if表达式是有值的,就是if或者else中最后一行语句返回的值。

·例如,val age = 30; if (age >
18) 1 else 0

·可以将if表达式赋予一个变量,例如,val isAdult = if
(age > 18) 1 else 0

·另外一种写法,var isAdult = -1; if(age
> 18) isAdult = 1 else isAdult = 0,但是通常使用上一种写法

·if表达式的类型推断:由于if表达式是有值的,而if和else子句的值类型可能不同,此时if表达式的值是什么类型呢?Scala会自动进行推断,取两个类型的公共父类型。

·例如,if(age > 18) 1 else 0,表达式的类型是Int,因为1和0都是Int

·例如,if(age > 18)
"adult" else 0,此时if和else的值分别是String和Int,则表达式的值是Any,Any是String和Int的公共父类型

·如果if后面没有跟else,则默认else的值是Unit,也用()表示,类似于java中的void或者null。例如,val age = 12; if(age >
18) "adult"。此时就相当于if(age > 18) "adult" else ()。

·将if语句放在多行中:默认情况下,REPL只能解释一行语句,但是if表达式通常需要放在多行。

·可以使用{}的方式,比如以下方式,或者使用:paste和ctrl+D的方式。

if(age > 18) { "adult"

} else if(age > 12) "teenager"
else "children"

语句终结符、块表达式

·默认情况下,scala不需要语句终结符,默认将每一行作为一个语句

·一行放多条语句:如果一行要放多条语句,则必须使用语句终结符

·例如,使用分号作为语句终结符,var a, b, c = 0;
if(a < 10) { b = b + 1; c = c + 1 }

·通常来说,对于多行语句,还是会使用花括号的方式

if(a < 10) {

b = b + 1

c = c + 1

}

·块表达式:块表达式,指的就是{}中的值,其中可以包含多条语句,最后一个语句的值就是块表达式的返回值。

·例如,var d = if(a < 10) { b = b
+ 1; c + 1 }

输入和输出

·print和printlnprint打印时不会加换行符,而println打印时会加一个换行符。

·例如,print("Hello
World"); println("Hello World")

·printfprintf可以用于进行格式化

·例如,printf("Hi, my name is
%s, I'm %d years old.\n", "Leo", 30)

·readLine: readLine允许我们从控制台读取用户输入的数据,类似于java中的System.in和Scanner的作用。

·综合案例:游戏厅门禁

val name = readLine("Welcome to Game
House. Please tell me your name: ")

print("Thanks. Then please tell me
your age: ")

val age = readInt()

if(age > 18) {

printf("Hi, %s, you are %d
years old, so you are legel to come here!", name, age)

} else {

printf("Sorry, boy, %s, you are
only %d years old. you are illegal to come here!", name, age)

}

循环

·while do循环:Scala有while do循环,基本语义与Java相同。

var n = 10

while(n > 0) {

println(n)

n -= 1

}

·Scala没有for循环,只能使用while替代for循环,或者使用简易版的for语句

·简易版for语句:var n = 10; for(i <- 1 to
n) println(i)

·或者使用until,表式不达到上限:for(i <- 1 until n)
println(i)

    ·也可以对字符串进行遍历,类似于java的增强for循环,for(c <- "Hello
World") print(c)

·跳出循环语句

    ·scala没有提供类似于java的break语句。

·但是可以使用boolean类型变量、return或者Breaks的break函数来替代使用。

import scala.util.control.Breaks._

breakable {

var n = 10

for(c <- "Hello
World") {

if(n == 5) break;

print(c)

n -= 1

}

}

高级for循环

·多重for循环:九九乘法表

for(i <- 1 to 9; j <- 1 to 9) {

if(j == 9) {

println(i * j)

} else {

print(i * j + "
")

}

}

·if守卫:取偶数

for(i <- 1 to 100 if i % 2 == 0)
println(i)

·for推导式:构造集合

for(i <- 1 to 10) yield i

函数的定义与调用

在Scala中定义函数时,需要定义函数的函数名、参数、函数体。

我们的第一个函数如下所示:

def sayHello(name: String, age: Int) = {

if (age > 18) { printf("hi
%s, you are a big boy\n", name); age }

else { printf("hi %s, you are a
little boy\n", name); age

}

sayHello("leo", 30)

Scala要求必须给出所有参数的类型,但是不一定给出函数返回值的类型,只要右侧的函数体中不包含递归的语句,Scala就可以自己根据右侧的表达式推断出返回类型。

在代码块中定义包含多行语句的函数体

单行的函数:def
sayHello(name: String) = print("Hello, " + name)

如果函数体中有多行代码,则可以使用代码块的方式包裹多行代码,代码块中最后一行的返回值就是整个函数的返回值。与Java中不同,不是使用return返回值的。

比如如下的函数,实现累加的功能:

def sum(n: Int) = {

var sum = 0;

for(i <- 1 to n) sum += i

sum

}

递归函数与返回类型

如果在函数体内递归调用函数自身,则必须手动给出函数的返回类型。

例如,实现经典的斐波那契数列:

9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5
+ 5 + 4; ....

def fab(n: Int): Int = {

if(n <= 1) 1

else fab(n - 1) + fab(n - 2)

}

默认参数

在Scala中,有时我们调用某些函数时,不希望给出参数的具体值,而希望使用参数自身默认的值,此时就定义在定义函数时使用默认参数。

def sayHello(firstName: String, middleName:
String = "William", lastName: String = "Croft") = firstName
+ " " + middleName + " " + lastName

如果给出的参数不够,则会从左往右依次应用默认参数。

def sayHello(name: String, age: Int = 20) {

print("Hello, " + name +
", your age is " + age)

}

sayHello("leo")

函数调用时带名参数

在调用函数时,也可以不按照函数定义的参数顺序来传递参数,而是使用带名参数的方式来传递。

sayHello(firstName = "Mick",
lastName = "Nina", middleName = "Jack")

还可以混合使用未命名参数和带名参数,但是未命名参数必须排在带名参数前面。

sayHello("Mick", lastName =
"Nina", middleName = "Jack")

变长参数

在Scala中,有时我们需要将函数定义为参数个数可变的形式,则此时可以使用变长参数定义函数。

def sum(nums: Int*) = {

var res = 0

for (num <- nums) res += num

res

}

sum(1, 2, 3, 4, 5)

序列作为变长参数

在如果想要将一个已有的序列直接调用变长参数函数,是不对的。比如val s = sum(1 to 5)。此时需要使用Scala特殊的语法将参数定义为序列,让Scala解释器能够识别。这种语法非常有用!一定要好好主意,在spark的源码中大量地使用到了。

val s = sum(1 to 5: _*)

案例:使用递归函数实现累加

def sum2(nums: Int*): Int = {

if (nums.length == 0) 0

else nums.head + sum2(nums.tail: _*)

}

过程

在Scala中,定义函数时,如果函数体直接包裹在了花括号里面,而没有使用=连接,则函数的返回值类型就是Unit。这样的函数就被称之为过程,即过程就是没有返回值的函数。

过程还有一种写法,就是将函数的返回值类型定义为Unit。

def sayHello(name: String) = "Hello,
" + name//函数

def sayHello(name: String) {
print("Hello, " + name); "Hello, " + name }//有值,但未使用=号,还是过程

def sayHello(name: String): Unit =
"Hello, " + name//有值,有=号,但强制返回类型为空,则还是过程

lazy值

在Scala中,提供了lazy值的特性,也就是说,如果将一个变量声明为lazy,则只有在第一次使用该变量时,变量对应的表达式才会发生计算。这种特性对于特别耗时的计算操作特别有用,比如打开文件进行IO,进行网络IO等。

import scala.io.Source._

lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString

即使文件不存在,也不会报错,只有第一个使用变量时会报错,证明了表达式计算的lazy特性。

val lines =
fromFile("C://Users//Administrator//Desktop//test.txt").mkString

lazy val lines =
fromFile("C://Users//Administrator//Desktop//test.txt").mkString

相当于定义了一个方法,只有在调用该方法时才会去执行方法体:

def lines =
fromFile("C://Users//Administrator//Desktop//test.txt").mkString

异常

在Scala中,异常处理和捕获机制与Java是非常相似的。

try {

throw new
IllegalArgumentException("x should not be negative")

} catch {

case _: IllegalArgumentException
=> println("Illegal Argument!")

} finally {

print("release
resources!")

}

Import java.io._

try {

throw new IOException(“io
exception!!!")

} catch {

case _: IllegalArgumentException
=> println("illegal argument")

}

try {

throw new IOException("user
defined exception")

} catch {

case e1: IllegalArgumentException
=> println("illegal argument")

case e2: IOException =>
println("io exception")

}

Array

在Scala中,Array代表的含义与Java中类似,也是长度不可改变的数组。此外,由于Scala与Java都是运行在JVM中,双方可以互相调用,因此Scala数组的底层实际上是Java数组。例如字符串数组在底层就是Java的String[],整数数组在底层就是Java的Int[]。

// 数组初始化后,长度就固定下来了,而且元素全部根据其类型初始化

val a = new Array[Int](10)

a(0)

a(0) = 1

val a = new Array[String](10)

// 可以直接使用Array()创建数组,元素类型自动推断

val a = Array("hello",
"world")

a(0) = "hi"

val a = Array("leo", 30)

ArrayBuffer

在Scala中,如果需要类似于Java中的ArrayList这种长度可变的集合类,则可以使用ArrayBuffer。

// 如果不想每次都使用全限定名,则可以预先导入ArrayBuffer类

import scala.collection.mutable.ArrayBuffer

// 使用ArrayBuffer()的方式可以创建一个空的ArrayBuffer

val b = ArrayBuffer[Int]()

// 使用+=操作符,可以添加一个元素,或者多个元素

// 这个语法必须要谨记在心!因为spark源码里大量使用了这种集合操作语法!

b += 1

b += (2, 3, 4, 5)

// 使用++=操作符,可以添加其他集合中的所有元素

b ++= Array(6, 7, 8, 9, 10)

// 使用trimEnd()函数,可以从尾部截断指定个数的元素

b.trimEnd(5)

// 使用insert()函数可以在指定位置插入元素

// 但是这种操作效率很低,因为需要移动指定位置后的所有元素

b.insert(5, 6)

b.insert(6, 7, 8, 9, 10)

// 使用remove()函数可以移除指定位置的元素

b.remove(1)

b.remove(1, 3)

// Array与ArrayBuffer可以互相进行转换

b.toArray

a.toBuffer

遍历Array和ArrayBuffer

// 使用for循环和until遍历Array / ArrayBuffer

// 使until是RichInt提供的函数

for (i <- 0 until b.length)

println(b(i))

// 跳跃遍历Array / ArrayBuffer

for(i <- 0 until (b.length, 2))

println(b(i))

// 从尾部遍历Array / ArrayBuffer

for(i <- (0 until b.length).reverse)

println(b(i))

// 使用“增强for循环”遍历Array / ArrayBuffer

for (e <- b)

println(e)

数组常见操作

// 数组元素求和

val a = Array(1, 2, 3, 4, 5)

val sum = a.sum

// 获取数组最大值

val max = a.max

// 对数组进行排序

scala.util.Sorting.quickSort(a)

// 获取数组中所有元素内容

a.mkString

a.mkString(", ")

a.mkString("<", ",",
">")

// toString函数

a.toString

b.toString

使用yield和函数式编程转换数组

// 对Array进行转换,获取的还是Array

val a = Array(1, 2, 3, 4, 5)

val a2 = for (ele <- a) yield ele * ele

// 对ArrayBuffer进行转换,获取的还是ArrayBuffer

val b = ArrayBuffer[Int]()

b += (1, 2, 3, 4, 5)

val b2 = for (ele <- b) yield ele * ele

// 结合if守卫,仅转换需要的元素

val a3 = for (ele <- if ele % 2 == 0)
yield ele * ele

// 使用函数式编程转换数组(通常使用第一种方式)

a.filter(_ % 2 == 0).map(2 * _)

a.filter { _ % 2 == 0 } map { 2 * _ }

算法案例:移除第一个负数之后的所有负数

// 构建数组

val a = ArrayBuffer[Int]()

a += (1, 2, 3, 4, 5, -1, -3, -5, -9)

// 每发现一个第一个负数之后的负数,就进行移除,性能较差,多次移动数组

var foundFirstNegative = false

var arrayLength = a.length

var index = 0

while (index < arrayLength) {

if (a(index) >= 0) {

index += 1

} else {

if (!foundFirstNegative)
{ foundFirstNegative = true; index += 1 }

else { a.remove(index);
arrayLength -= 1 }

}

}

算法案例:移除第一个负数之后的所有负数(改良版)

// 重新构建数组

val a = ArrayBuffer[Int]()

a += (1, 2, 3, 4, 5, -1, -3, -5, -9)

// 每记录所有不需要移除的元素的索引,稍后一次性移除所有需要移除的元素

// 性能较高,数组内的元素迁移只要执行一次即可

var foundFirstNegative = false

val keepIndexes = for (i <- 0 until
a.length if !foundFirstNegative || a(i) >= 0) yield {

if (a(i) < 0) foundFirstNegative
= true

i

}

for (i <- 0 until keepIndexes.length) {
a(i) = a(keepIndexes(i)) }

a.trimEnd(a.length - keepIndexes.length)

创建Map

// 创建一个不可变的Map

val ages = Map("Leo" -> 30,
"Jen" -> 25, "Jack" -> 23)

ages("Leo") = 31

// 创建一个可变的Map

val ages =
scala.collection.mutable.Map("Leo" -> 30, "Jen" ->
25, "Jack" -> 23)

ages("Leo") = 31

// 使用另外一种方式定义Map元素

val ages = Map(("Leo", 30),
("Jen", 25), ("Jack", 23))

// 创建一个空的HashMap

val ages = new scala.collection.mutable.HashMap[String,
Int]

访问Map的元素

// 获取指定key对应的value,如果key不存在,会报错

val leoAge = ages("Leo")

val leoAge = ages("leo")

// 使用contains函数检查key是否存在

val leoAge = if
(ages.contains("leo")) ages("leo") else 0

// getOrElse函数

val leoAge = ages.getOrElse("leo",
0)

修改Map的元素

// 更新Map的元素

ages("Leo") = 31

// 增加多个元素

ages += ("Mike" -> 35,
"Tom" -> 40)

// 移除元素

ages -= "Mike"

// 更新不可变的map

val ages2 = ages + ("Mike" ->
36, "Tom" -> 40)

// 移除不可变map的元素

val ages3 = ages - "Tom"

遍历Map

// 遍历map的entrySet

for ((key, value) <- ages) println(key +
" " + value)

// 遍历map的key

for (key <- ages.keySet) println(key)

// 遍历map的value

for (value <- ages.values)
println(value)

// 生成新map,反转key和value

for ((key, value) <- ages) yield (value,
key)

SortedMap和LinkedHashMap

// SortedMap可以自动对Map的key的排序

val ages =
scala.collection.immutable.SortedMap("leo" -> 30,
"alice" -> 15, "jen" -> 25)

// LinkedHashMap可以记住插入entry的顺序

val ages = new
scala.collection.mutable.LinkedHashMap[String, Int]

ages("leo") = 30

ages("alice") = 15

ages("jen") = 25

Map的元素类型—Tuple

// 简单Tuple

val t = ("leo", 30)

// 访问Tuple

t._1

// zip操作

val names = Array("leo",
"jack", "mike")

val ages = Array(30, 24, 26)

val nameAges = names.zip(ages)

for ((name, age) <- nameAges)
println(name + ": " + age)

定义一个简单的类

// 定义类,包含field以及方法

class HelloWorld {

private var name = "leo"

def sayHello() { print("Hello,
" + name) }

def getName = name

}

// 创建类的对象,并调用其方法

val helloWorld = new HelloWorld

helloWorld.sayHello()

print(helloWorld.getName) // 也可以不加括号,如果定义方法时不带括号,则调用方法时也不能带括号

getter与setter

1、定义不带private的 var field,此时scala生成class时,会自动生成一个private[this]的成员字段(名称与field不同),并还生成一对getter和setter方法,分别叫做field和 field_=,并且getter和setter方法的访问修饰符与field定义相同

2、而如果使用private修饰field,则只生成的getter和setter,且访问修饰也是private的

3、如果定义val field,则只会生成getter方法

4、
如果不希望生成setter和getter方法,则将field声明为private[this]

class Student {

var name = "leo"

}

// 调用getter和setter方法,分别叫做name和name_=

val leo = new Student

print(leo.name)

leo.name = "leo1" //实际上会调用 leo.name_=("leo1")方法

自定义getter与setter

// 如果只是希望拥有简单的getter和setter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:var、val、private、private[this]

// 但是如果希望能够自己对getter与setter进行控制,则可以自定义getter与setter方法

// 自定义setter方法的时候一定要注意scala的语法限制,签名、=、参数间不能有空格

class Student {

private var myName = "leo"
//默认会生成一对private
getter(myName)、setter(myName
_=)方法

def name = "your name is "
+ myName //自定义myName
成员变量getter方法

def name_=(newValue: String) 
{//自定义myName 成员变量的setter方法

print("you cannot
edit your name!!!")

}

}

val leo = new Student

print(leo.name)

leo.name = "leo1" //会去调用 name_+ 自定义setter 方法

仅暴露field的getter方法

// 如果你不希望field有setter方法,则可以定义为val,但是此时就再也不能更改field的值了

// 但是如果希望能够仅仅暴露出一个getter方法,并且还能通过某些方法更改field的值,那么需要综合使用private以及自定义getter方法。此时,由于field是private的,所以setter和getter都是private,对外界没有暴露;自己可以实现修改field值的方法;自己可以覆盖getter方法

class Student {

private var myName = "leo"

def updateName(newName: String) { //更改field的其他方法(命名约束的不满足setter方法)

if(newName ==
"leo1") myName = newName

else print("not
accept this new name!!!")

}

def name = "your name is"
+ myName  //覆盖自动生成的私有getter方法

}

private[this]的使用

// 如果将field使用private来修饰,那么代表这个field是类私有的,在类的方法中,可以直接访问类的其他对象的private
field

// 这种情况下,如果不希望field被其他对象访问到,那么可以使用private[this],意味着对象私有的field,只有本对象内可以访问到(子类对象中也是不可以访问的,因为私有的是不能被继承的)

class Student {

private var myAge = 0 //试着修改成private[this]

def age_=(newValue: Int) {

if (newValue > 0)
myAge = newValue

else print("illegal
age!")

}

def age = myAge

def older(s: Student) = {

myAge > s.myAge //修改成private[this]后,就会报错

}

}

private[this]还可以用为修改方法

Java风格的getter和setter方法

// Scala的getter和setter方法的命名与java是不同的,是field和field_=的方式

// 如果要让scala自动生成java风格的getter和setter方法,只要给field添加@BeanProperty注解即可

// 此时会生成4个方法,name:
String、name_=(newValue:
String): Unit、getName():
String、setName(newValue:
String): Unit

import scala.reflect.BeanProperty

class Student {

@BeanProperty var name: String = _

}

class Student(@BeanProperty var name:
String)

val s = new Student

s.setName("leo")

s.getName()

辅助constructor

// Scala中,可以给类定义多个辅助constructor,类似于java中的构造函数重载

// 辅助constructor之间可以互相调用,而且必须第一行调用主constructor或其他辅构造器

class Student {

private var name = ""

private var age = 0

def this(name: String) {

this()

this.name = name

}

def this(name: String, age: Int) {

this(name)

this.age = age

}

}

主constructor

// Scala中,主constructor是与类名放在一起的,有且只有一个,与java不同

// 而且类中,没有定义在任何方法中的代码(包括成员字段),都属于主constructor的代码,且执行的顺序与代码书写的顺序一致。这其实与Java是一样的,在Java中方法之外的代码(成员以及代码块)会在构造器调用之前最先执行,姑且将这些代码看作也是放到了一个主构造器中进行执行的,只不过这种主构造器不能带构造参数

//主构造器与类定义是在一起的,如果有参数,则在类名后面跟括号即可:

class Student(val name: String, val age:
Int) {

println("your name is " +
name + ", your age is " + age)

}

当然没有参数的主构造器也可以带括号:

class Student() {}

// 主constructor中还可以通过使用默认参数,来给参数默认的值

class Student(val name: String =
"leo", val age: Int = 30) {

println("your name is " +
name + ", your age is " + age)

}

// 如果主constrcutor传入的参数什么修饰都没有,比如name: String,那么如果类内部除主constrcutor方法外其它方法也使用到了,则会自动将该参数修饰为private[this] name以便其它方法使用:

class Student(name: String) {

def
f(){print(name)}

def
f(s:Student){print(s.name)}//编译出错

}

class Student(val name: String) {

def
f(){print(name)}

def
f(s:Student){print(s.name)}//编译正确,证明没有使用var或val修饰时,且在除主构造器中使用外,则使用private[this]来修饰

}

类中没有定义的在任何方法中的代码都属于主构造器,并且执行顺序与书写顺序一致:

Scala编程基础

内部类

// Scala中,同样可以在类中定义内部类;但是与java不同的是,每个外部类的对象的内部类,都属于不同的类

import scala.collection.mutable.ArrayBuffer

class Class {

class Student(val name: String) {}

val students = new
ArrayBuffer[Student]

def getStudent(name: String) = 
{

new Student(name)

}

}

val c1 = new Class

val s1 = c1.getStudent("leo")

c1.students += s1

val c2 = new Class

val s2 = c2.getStudent("leo")

c1.students += s2   //异常

object

1、object,相当于class的单个实例(但与从class实例化出来的对象的内容决不一样),通常在里面放一些class层面上共享的内容,如Java中的静态field或者method即在定义在object中(注:Scala中没有Java的静态概念,所以延伸出了object这个东东)

2、你可以将object看作是一个类class,只是这个类在内存中只有一个单例,且定义的object名就是实例名,不需我们自己实例化,运行时JVM已帮我们new出来了

3、第一次调用object的方法时,就会执行object的constructor,也就是object内部不在method中的代码;但是object不能定义接受参数的constructor

4、注意,object的constructor只会在其第一次被调用时执行一次,以后再次调用就不会再次执行constructor了

5、object通常用于作为单例模式的实现,或者放class的静态成员,比如工具方法

object Person {

private var eyeNum = 2

println("this Person
object!")

def getEyeNum = eyeNum

}

伴生对象

// 如果有一个class,还有一个与class同名的object,那么就称这个object是class的伴生对象,class是object的伴生类

// 伴生类和伴生对象必须存放在一个.scala文件之中

// 伴生类和伴生对象,最大的特点就在于,互相可以访问private field

object Person {

private val eyeNum = 2

def getEyeNum = eyeNum

}

class Person(val name: String, val age:
Int) {

def sayHello = println("Hi,
" + name + ", I guess you are " + age + " years old!"
+ ", and usually you must have " + Person.eyeNum
+ " eyes.")

}

让object继承抽象类

// object的功能其实和class类似,除了不能定义接受参数的constructor之外

// object也可以继承抽象类,并覆盖抽象类中的方法

abstract class Hello(var message: String) {

def sayHello(name: String): Unit

}

object HelloImpl extends
Hello("hello") {

override def sayHello(name: String)
= {

println(message +
", " + name)

}

}

apply方法

// object中非常重要的一个特殊方法,就是apply方法

// 通常在伴生对象中实现apply方法,并在其中实现构造伴生类的对象的功能,一般用作工厂方法

// 而创建伴生类的对象时,通常不会使用new Class的方式,而是使用Class()的方式,隐式地调用伴生对象得apply方法,这样会让对象创建更加简洁

// 比如,Array类的伴生对象的apply方法就实现了接收可变数量的参数,并创建一个Array对象的功能

val a = Array(1, 2, 3, 4, 5)

// 比如,定义自己的伴生类和伴生对象

class Person(val name: String)

object Person {

def apply(name: String) = new
Person(name)

}

另外,如果直接在一个对象后面接小括号,则会去调用这个对象所对应类中相应的apply方法:

class Person {

def apply(name: String) =
println(name)

}

scala> val p = new Person

scala> p("Persion")

Persion

main方法

// 就如同java中,如果要运行一个程序,必须编写一个包含main方法类一样;在scala中,如果要运行一个应用程序,那么必须有一个main方法,作为入口

// scala中的main方法定义为def main(args: Array[String]),而且必须定义在object中

object HelloWorld {

def main(args: Array[String]) {

println("Hello
World!!!")

}

}

// 除了自己实现main方法之外,还可以继承App Trait,然后将需要在main方法中运行的代码,直接作为object的constructor代码;而且用args可以接受传入的参数

object HelloWorld extends App {

if (args.length > 0)
println("hello, " + args(0))

else println("Hello
World!!!")

}

// 如果要运行上述代码,需要将其放入.scala文件,然后先使用scalac编译,再用scala执行

scalac HelloWorld.scala

scala -Dscala.time HelloWorld

// App Trait的工作原理为:App Trait继承自DelayedInit Trait,scalac命令进行编译时,会把继承App Trait的object的constructor代码都放到DelayedInit Trait的delayedInit方法中,然后由App Trait的main方法去调用执行

用object来实现枚举功能

// Scala没有直接提供类似于Java中的Enum这样的枚举特性,如果要实现枚举,则需要用object继承Enumeration类,并且调用Value方法来初始化枚举值

object Season extends Enumeration {

val SPRING, SUMMER, AUTUMN, WINTER =
Value

}

// 还可以通过Value传入枚举值的id和name,通过id和toString可以获取; 还可以通过id和name来查找枚举值

object Season extends Enumeration {

val SPRING = Value(0,
"spring")

val SUMMER = Value(1,
"summer")

val AUTUMN = Value(2,
"autumn")

val WINTER = Value(3,
"winter")

}

Season(0)  // spring

Season.withName("spring") //
spring,根据名称找

// 使用枚举object.values可以遍历枚举值

for (ele <- Season.values) println(ele)

extends

// Scala中,让子类继承父类,与Java一样,也是使用extends关键字

// 继承就代表,子类可以从父类继承父类的field和method;然后子类可以在自己内部放入父类所没有,子类特有的field和method;使用继承可以有效复用代码

// 子类可以覆盖父类的field和method;但要注意的是final类是不能被继承的,而且final类型的field和method是无法被覆盖的

class Person {

private var name = "leo"

def getName = name

}

class Student extends Person {

private var score = "A"

def getScore = score

}

override和super

// Scala中,如果子类要覆盖一个父类中的非抽象方法,则必须使用override关键字;如果是抽象的方法,则可以省略

// override关键字可以帮助我们尽早地发现代码里的错误,比如:override修饰的父类方法的方法名我们拼写错了;比如要覆盖的父类方法的参数我们写错了;等等

// 此外,在子类覆盖父类方法之后,如果我们在子类中就是要调用父类的被覆盖的方法呢?那就可以使用super关键字,显式地指定要调用父类的方法

class Person {

private var name = "leo"

def getName = name

}

class Student extends Person {

private var score = "A"

def getScore = score

override def getName = "Hi, I'm
" + super.getName

}

重写时需要override关键字,如果是实现则可以省略override关键字

override
field

子类可以覆盖父类的同名的非private成员

// Scala中,子类可以覆盖父类的val field,而且子类的val field还可以覆盖父类的val field的getter方法;只要在子类中使用override关键字即可

class Person {

/*private*/ val name: String =
"Person"

}

class Student extends Person {

override val name: String =
"leo" // 重写一定要带上override关键字

}

只有val变量才能被重写,var变量是不能被重写的:

class A{

var
f = "a"

}

class B extends A{

override
var f = "b" // error: overriding variable f in class A of type
String;

//variable
f cannot override a mutable variable

}

下面也是不行的:

class A{

var
f:String = "a"

}

class B extends A{

override
def f:String = "b"

override
def f_=(x:String) = println(x) // error: overriding variable f in class A of
type String;

//method f_= cannot override a mutable variable

}

var变量只能被实现,如果将上面换成抽象的var字段,则是可以的:

abstract class A{

var
f:String

}

class B extends A{

/*
override */ def f:String = "b" //由于是实现,所以可以省略override

override
def f_=(x:String) = println(x) //也可以不省略override

}

或者:

abstract class A{

var
f:String

}

class B extends A{

var
f:String = ""

}

val变量只能被val实现,不能被def实现:

abstract class A{

val
f:String

}

class B extends A{

def
f:String = ""  // error: overriding value f in class A of type
String;

//method
f needs to be a stable, immutable value

}

但可以这样:

abstract class A{

val
f:String

}

class B extends A{

val
f:String = ""

}

val、var override/实现 def

abstract class Person {

def id: Int

}

class Student extends Person{

override var id =
9527  //Error: method id_= overrides nothing

}

在scala中定义了一个var变量,会自动生成getter和setter方法。由于父类中只定义了一个方法def id: Int,而子类中var变量会自动生成getter(id)与setter方法(id_),但是父类并没有这个setter方法,所以是无法重写的。如下修改即可:

abstract class Person {
    def id: Int  
    def id_=(value: Int) //父类必须有set方法
}
class Student extends Person{
    override var id = 9527 //为var变量自动生成get和set方法
}

或者子类定义成val变量:

abstract class Person {
    def id: Int  
}
class Student extends Person{
    override val id = 9527
}
 
上面是val或var来实现def,下面是val或var来重写def:
class Person {
    def id: Int = 1
}
class Student extends Person{
    override val id = 9527 
}
class Person {
    def id: Int = 1
    def id_=(value: Int) =println(value) 
}
class Student extends Person{
    override var id = 9527 
}
 
但是不能使用def重写val或var:
class Person {
  val sex: String 
}
class Student extends Person {
  override def sex:String = "" // error: overriding value sex in class Person of type String;
                                      //method sex needs to be a stable, immutable value
}
 
class Person {
  var sex: String = "X"
}
class Student extends Person {
  override def sex:String = "" 
  override def sex_=(x:String) = println(x)
}
也不能使用def实现val:
abstract class Person {
  val sex: String 
}
class Student extends Person {
  def sex:String = "" // error: overriding value sex in class Person of type String;
                               //method sex needs to be a stable, immutable value
}
但可以使用def实现var:
abstract class Person {
  var sex: String 
}
class Student extends Person {
  def sex:String = ""
  def sex_=(x:String) = println(x)
}
 
成员变量与方法之间重写与实现结论:可以使用val或var来重写或实现def,也可以使用def实现var;但不能使用def重写val或var,也不能使用def实现val

isInstanceOf和asInstanceOf

// 如果我们创建了子类的对象,但是又将其赋予了父类类型的变量。则在后续的程序中,我们又需要将父类类型的变量转换为子类类型的变量,应该如何做?

// 首先,需要使用isInstanceOf判断对象是否是指定类的对象,如果是的话,则可以使用asInstanceOf将对象转换为指定类型

// 注意,如果对象是null,则isInstanceOf一定返回false,asInstanceOf一定返回null

// 注意,如果没有用isInstanceOf先判断对象是否为指定类的实例,就直接用asInstanceOf转换,则可能会抛出异常

class Person

class Student extends Person

val p: Person =  new Student

var s: Student = null

if (p.isInstanceOf[Student]) s =
p.asInstanceOf[Student]

scala> p.isInstanceOf[Student]

res7: Boolean = true

scala> p.isInstanceOf[Person]

res8: Boolean = true

getClass和classOf

// isInstanceOf只能判断出对象是否是给定类或其子类的实例对象,而不能精确判断出对象就是给定类的实例对象

// 如果要求精确地判断对象就是指定类的对象,那么就只能使用getClass和classOf了

// 对象.getClass可以精确获取对象所属的类class,classOf[类]可以精确获取类,然后使用==操作符即可判断

class Person

class Student extends Person

val p: Person = new Student

p.isInstanceOf[Person]

p.getClass == classOf[Person]

p.getClass == classOf[Student]

使用模式匹配进行类型判断

// 但是在实际开发中,比如spark的源码中,大量的地方都是使用了模式匹配的方式来进行类型的判断,这种方式更加地简洁明了,而且代码得可维护性和可扩展性也非常的高

// 使用模式匹配,功能性上来说,与isInstanceOf一样,也是判断主要是该类以及该类的子类的对象即可,也不是精准判断的

class Person

class Student extends Person

val p: Person = new Student

p match {

case per: Person =>
println("it's Person's object")

case _  =>
println("unknown type")

}

protected

// 跟java一样,scala中同样可以使用protected关键字来修饰field和method,这样子类就可以继承这些成员或方法

// 还可以使用protected[this],则只能在当前子类对象中访问父类的使用protected[this]修饰的field和method,无法通过其他子类对象访问父类中的这些字段与方法

class Person {

protected var name: String =
"leo"

protected[this] var hobby: String =
"game"

}

class Student extends Person {

def sayHello = println("Hello,
" + name)

def makeFriends(s: Student) {

println("my hobby
is " + hobby + ", your hobby is " + s.hobby)
//此处编译出错

}

}

protected[this]修饰的字段只能在本对象或其子对象中使用,不能在其他对象中使用:

class Person {

protected var name: String =
"leo"

protected[this] var hobby: String =
"game"

def makeFriends(s: Person ){

println("my hobby
is " + hobby + ", your hobby is " + s.hobby)
//此处编译还是出错

}

}

与private[this]一样,protected[this]也可以修饰方法

调用父类的constructor

// Scala中,每个类可以有一个主constructor和任意多个辅助constructor,而每个辅助constructor的第一行都必须是调用其他辅助constructor或者是主constructor;因此子类的辅助constructor是一定不可能直接调用父类的constructor的

// 只能在子类的主constructor中调用父类的constructor,以下这种语法,就是通过子类的主构造函数来调用父类的构造函数(即在extends后面指定需要调用父类哪个构造器)

// 注意!如果是父类中接收的参数,比如name和age,子类中接收时,就不要用任何val或var来修饰了(或者带上修饰了,但将参数名命成不一样也可),否则会认为是子类要覆盖父类的field

class Person(val name: String, val age:
Int)

class Student(name: String, age: Int, var
score: Double) extends Person(name, age) /*调用父类的辅助构造器*/{

def this(name: String) {

this(name, 0, 0) //调用主构造器

}

def this(age: Int) {

this("leo",
age, 0) //调用主构造器

}

}

调用父类的主构造器:

class Person(val name: String, val age:
Int){

def
this(){

this("11",11)

}

}

class Student(name: String, age: Int, var
score: Double) extends Person/*或 Person()*/ {

def this(name: String) {

this(name, 0, 0) //调用主构造器

}

def this(age: Int) {

this("leo",
age, 0) //调用主构造器

}

}

匿名内部类

// 在Scala中,匿名子类是非常常见,而且非常强大的。Spark的源码中也大量使用了这种匿名子类。

// 匿名子类,也就是说,可以定义一个类的没有名称的子类,并直接创建其对象,然后将对象的引用赋予一个变量。之后甚至可以将该匿名子类的对象传递给其他函数使用。

class Person(protected val name: String) {

def sayHello = "Hello, I'm
" + name

}

val p = new Person("leo") {

override def sayHello = "Hi,
I'm " + name

}

def greeting(p: Person { def sayHello:
String }) {

println(p.sayHello)

}

抽象类

// 如果在父类中,有某些方法无法立即实现,而需要依赖不同的子来来覆盖,重写实现自己不同的方法实现。此时可以将父类中的这些方法不给出具体的实现,只有方法签名,这种方法就是抽象方法。

// 而一个类中如果有一个抽象方法,那么类就必须用abstract来声明为抽象类,此时抽象类是不可以实例化的

// 在子类中覆盖抽象类的抽象方法时,不需要使用override关键字(也可带上),但如果是重写父类具体方法或成员,则不能省略override

abstract只能修饰类,不能修饰成员与方法,哪怕成员(没有初始化)与方法(没有方法体)是抽象的

abstract class Person(val name: String) {

def sayHello: Unit

}

class Student(name: String) extends
Person(name) {

def sayHello: Unit =
println("Hello, " + name)

}

抽象field

// 如果在父类中,定义了field,但是没有给出初始值,则此field为抽象field

// 抽象field意味着,scala会根据自己的规则,为var或val类型的field生成对应的getter和setter方法,但是父类中是没有该field的

// 子类必须覆盖field,以定义自己的具体field,并且覆盖抽象field,不需要使用override关键字

abstract class Person {

val name: String

}

class Student extends Person {

val name: String = "leo"

}

没有初始化的成员所在的类要是抽象类:

abstract class A{

var
a:String

}

/*class B extends A*/编译时报错:需要重写父类的抽象成员

class B extends A{

/*override*/
var a:String = "a" //也可以省略override

}

除了通过上面直接覆盖父类的抽象成员外,还可以简接通过实现抽象成员所对应的getter与setter方法即可:

class B extends A{

/*override*/
def a = "a" //由于是实现,所以可以省略override

override
def a_=(x:String){println(a)}

}

上面是通过实现父类抽象成员所对应的getter与setter方法来重写抽象成员,所以可以看出:没有被初始化的成员所对应的getter与setter方法实质上就是抽象的,所以类要定义是abstract,成员字段本身没有什么抽象不抽象的概念

将trait作为接口使用

// Scala中的Triat是一种特殊的概念

// 首先我们可以将Trait作为接口来使用,此时的Triat就与Java中的接口非常类似

// 在triat中可以定义抽象方法,就与抽象类中的抽象方法一样,只要不给出方法的具体实现即可

// 类可以使用extends关键字继承trait,注意,这里不是implement,而是extends,在scala中没有implement的概念,无论继承类还是trait,统一都是extends

// 类继承trait后,必须实现其中的抽象方法(如果是trait继承trait则不需要,这好比Java中的接口继承接口一样),实现时不需要使用override关键字

// scala不支持对类进行多继承,但是支持多重继承trait,使用with关键字即可

trait HelloTrait {

def sayHello(name: String)

}

trait MakeFriendsTrait {

def makeFriends(p: Person)

}

class Person(val name: String) extends
HelloTrait with MakeFriendsTrait with Cloneable {

def sayHello(name: String) =
println("Hello, " + name)

def makeFriends(p: Person) =
{sayHello(name);println("Hello, my name is " + name + ", your
name is " + p.name)}

}

val p1 = new Person("leo")

val p2 = new Person("lily")

p1.makeFriends(p2)

在Trait中定义具体方法

// Scala中的Triat可以不是只定义抽象方法,还可以定义具体方法,此时trait更像是包含了通用工具方法的东西

// 有一个专有的名词来形容这种情况,就是说trait的功能混入了类

// 举例来说,trait中可以包含一些很多类都通用的功能方法,比如打印日志等等,spark中就使用了trait来定义了通用的日志打印方法

trait Logger {

def log(message: String) =
println(message)

}

class Person(val name: String) extends
Logger {

def makeFriends(p: Person) {

println("Hi, I'm
" + name + ", I'm glad to make friends with you, " + p.name)

log("makeFriends
methdo is invoked with parameter Person[name=" + p.name + "]")

}

}

val p1 = new Person("leo")

val p2 = new Person("lily")

p1.makeFriends(p2)

在Trait中定义具体字段

// Scala中的Triat可以定义具体field,此时继承trait的类就自动获得了trait中定义的field

trait Person {

val eyeNum: Int = 2

}

class Student(val name: String) extends
Person {

def sayHello = println("Hi, I'm
" + name + ", I have " + eyeNum + " eyes.")

}

val s = new Student("leo")

s.sayHello

在Trait中定义抽象字段

// Scala中的Triat可以定义抽象field,而trait中的具体方法则可以基于抽象field来编写

// 但是继承trait的类,则必须覆盖抽象field,提供具体的值

trait SayHello {

val msg: String //抽象字段

def sayHello(name: String) =
println(msg + ", " + name) // 具体方法调用抽象字段(实质上是调用val抽象字段所对应的getter抽象方法),相当于Java中的模式方法,另参看这里

}

class Person(val name: String) extends
SayHello {

val msg: String = "hello"

def makeFriends(p: Person) {

sayHello(p.name)

println("I'm "
+ name + ", I want to make friends with you!")

}

}

val p1 = new Person("leo")

val p2 = new Person("lily")

p1.makeFriends(p2)

为实例混入trait

// 有时我们可以在创建类的对象时,指定该对象混入某个trait,这样,就只有这个对象混入该trait的方法,而类的其他对象则没有

trait Logged {

def log(msg: String) {}

}

trait MyLogger extends Logged {

override def log(msg: String) {
println("log: " + msg) }

}

class Person(val name: String) extends
Logged {

def sayHello {
println("Hi, I'm " + name); log("sayHello is invoked!") }

}

val p1 = new Person("leo")

p1.sayHello  // Hi, I'm leo

val p2 = new Person("jack") with MyLogger //实例化时混入

p2.sayHello  // Hi, I'm jack

//log: sayHello
is invoked!

trait调用链

// Scala中支持让类继承多个trait后,依次调用多个trait中的同一个方法,只要让多个trait的同一个方法中,在方法最后都执行“super.方法”来调用父类方法即可

// 类中调用多个trait中都有的这个方法时,首先会从最右边的trait的方法开始执行,然后依次往左执行,形成一个调用链条

// 这种特性非常强大,其实就相当于设计模式中的责任链模式的一种具体实现

trait Handler {

def handle(data: String) {}

}

trait DataValidHandler extends Handler {

override def handle(data: String) {

println("check
data: " + data)

super.handle(data)

}

}

trait SignatureValidHandler extends Handler
{

override def handle(data: String) {

println("check
signature: " + data)

super.handle(data)

}

}

class Person(val name: String) extends
SignatureValidHandler with DataValidHandler {

def sayHello = {
println("Hello, " + name); handle(name) }

}

val p = new Person("leo")

p.sayHello

Hello, leo

check data: leo

check signature: leo

在trait中覆盖抽象方法

// 在trait中,是可以覆盖父trait的抽象方法的

// 但是覆盖时,如果使用了“super.方法”形式调用了父类抽象方法,则无法通过编译。因为super.方法就会去掉用父trait的抽象方法,此时子trait的该方法还是会被认为是抽象的,所以在override的同时还需要加上abstract

// 此时如果要通过编译,就得给子trait的方法加上abstract override修饰

trait Logger {

def log(msg: String)

}

trait MyLogger extends Logger {

abstract
override def log(msg: String) { println("MyLogger.log()");super.log(msg) }

}

class BasicLog extends Logger{

def log(msg:
String) { println("BasicLog.log()"); println(msg) }

}

class Person(val name: String) extends
BasicLog with MyLogger {

def makeFriends(p: Person) {

println("Hi, I'm
" + name + ", I'm glad to make friends with you, " + p.name)

log("makeFriends
methdo is invoked with parameter Person[name=" + p.name + "]")

}

}

val p1 = new Person("leo")

val p2 = new Person("lily")

p1.makeFriends(p2)

Hi, I'm leo, I'm glad to make friends with
you, lily

MyLogger.log()

BasicLog.log()

makeFriends methdo is invoked with
parameter Person[name=lily]

混合使用trait的具体方法和抽象方法

// 在trait中,可以混合使用具体方法和抽象方法

// 可以让具体方法依赖于抽象方法,而抽象方法则放到继承trait的类中去实现

// 这种trait其实就是设计模式中的模板设计模式的体现

trait Valid {

def getName: String  //抽象方法

def valid: Boolean = { //具体方法中调用抽象方法,相当于Java中的模板方法

getName ==
"leo"

}

}

class Person(val name: String) extends
Valid {

println(valid)

def getName = name

}

val p = new Person("leo") //true

trait的构造机制

// 在Scala中,trait也是有构造代码的,也就是trait中的,不包含在任何方法中的代码

// 而继承了trait的类的构造机制如下:1、父类的构造函数执行;2、trait的构造代码执行,多个trait从左到右依次执行;3、构造trait时会先构造父trait,如果多个trait继承同一个父trait,则父trait只会构造一次;4、所有trait构造完毕之后,子类的构造函数执行

class Person { println("Person's
constructor!") }

trait Logger { println("Logger's
constructor!") }

trait MyLogger extends Logger {
println("MyLogger's constructor!") }

trait TimeLogger extends Logger { println("TimeLogger's
constructor!") }

class Student extends Person with MyLogger
with TimeLogger {

println("Student's
constructor!")

}

val s = new Student

trait field的初始化

// 在Scala中,trait的构造函数是不能接参数的(包括主构造器与辅助构造器),即trait不能定义辅助构造器,这是trait与class的唯一区别,但是如果需求就是要trait能够对field进行初始化,该怎么办呢?只能使用Scala中非常特殊的一种高级特性——提前定义

trait SayHello {

val msg: String

println("1、SayHello")

println(msg.toString) // 抛NullPointerException异常。由于在调用msg成员字段时,发现在msg是被重新实现(或重写,这里为实现),则会去调用子类中的实现的msg成员,但由于此时子类构造器还未执行,所以子类msg还没来得及初始化,所以返回null,最终导致空指针异常

}

class Person extends SayHello{

println("2、Person")

val msg:String = "init"

}

new Person // 抛NullPointerException异常,原因父trait构造代码会先于子类构造器执行,在执行msg.toString时子类中的msg还没有来得及初始化。但如果将上面的val都修改为def,则可以正常运行。因为初始化父类时,由于子类实现(或重写,这里为实现)了msg方法,所以msg.toString会去调用子类实现的msg方法而返回"init",即使此时子类还没有被初始化:

trait SayHello {

def msg: String

println("1、SayHello")

println(msg.toString)

}

class Person extends SayHello{

println("2、Person")

def msg:String = "init"

}

new Person

即使父类提供了初始化,但还是抛NullPointerException,原因是子类重写了父类该字段msg,在执行父类构造器中的msg.toString时,msg使用的是子类中被重写过的,但此时子类构造器还未被执行,所以子类的msg此时还为null:

trait SayHello {

val msg: String = "000"

println("1、SayHello")

println(msg.toString) // NullPointerException

}

class Person extends SayHello{

println("2、Person")

override val msg:String =
"init"

}

new Person

而下面的则不会抛异常了,原因是子类没有重写msg字段,所以父类构造器在执行时,msg使用的还是父中的msg,且已经被初始化过了:

trait SayHello {

val msg: String = "000"

println("1、SayHello")

println(msg.toString) // 不会抛异常,注意:此名要放在上面msg初始化语句的后面,否则还是会抛空指针

}

class Person extends SayHello{

println("2、Person")

}

new Person

下面根据前面的知识(字段与方法相互实现与重写),结合上面的经验,分析分析一下下面的情况:

以下也可以,原因也是通过方法的多态来初始化:

trait SayHello {

var msg: String

println("1、SayHello")

println(msg.toString)//会去调用子类实现方法msg,顺利执行

}

class Person extends SayHello{

println("2、Person")

def msg:String =
{println("person.msg");"init" }

def msg_=(x:String) = println(x)

}

new Person

trait SayHello {

var msg:
String

println("1、SayHello")

println(msg.toString) // 抛 NullPointerException,原因父类中的msg被子类实现过,但父类调用时,子类还未初始msg字段

}

class Person extends SayHello{

println("2、Person")

var msg:String
= "init"

}

new Person

上面除了通过调用子类实现(或重写)方法解决问题外,下面还可以通过提前定义方式来初始化:

trait SayHello {

val msg: String

println("3、SayHello")

println(msg.toString)

}

class Person{println("2、Person")}

val p = new {

val msg: String = {println("1、init");"init"} 
// 实例化时提前初始化

} with Person with SayHello

1、init -> 2、Person -> 3、SayHello

注意上面new
… with与class …extends…with的区别,new…with是动态混入,执行构造器是从new后面的类(或块,这里为块)开始从左到右依次执行;而class…extends…with则是静态混入,在定义class时就已确定,其构造器是从extends后面的类开始从左往右依次执行,执行完后最后执行class 后面指定的类的构造器。如下面的new … with形式构造顺序:

trait A{

println("a")

}

class B extends A{

println("b")

}

trait C extends A{

println("c")

}

new B with C // a -> b -> c

class…extends…with构造顺序:

trait A{

println("a")

}

trait B{

println("b")

}

class C extends A with B{

println("c")

}

new C // a -> b -> c

下面是另一种初始化方式(class …extends…with静态定义方式),此种方式比上面初始化方式好理解一点:

trait SayHello {

val msg: String

println("2、SayHello")

println(msg.toString)

}

class Person extends {

val msg: String = {println("1、init");"init"}
// 类定义时提前初始化

} with SayHello {

println("3、Person")

}

new Person

// 另外一种方式就是使用lazy value

trait SayHello {

lazy val msg: String =
{println("SayHello");null} // 此句不会执行

println(msg.toString) // 此句会调用子类重写过的msg成员,由于子类msg定义成了lazy,而lazy变量有个特性就是在使用时会执行右边表达式,所以在这里调用msg.toString方法时,就会触发懒加载右边的计算表达式,所以lazy字段不是由类来初始化的,而是由调用时机来决定,所以子类中的lazy
msg会先于子类其他成员被初始化

println("2")

}

class Person extends SayHello {

println("3")

val m: String =
{println("4");"m"}

override lazy val msg: String =
{println("1");"init"}

}

new Person

1

init

2

3

4

trait继承class

// 在Scala中,trait也可以继承自class,此时这个class就会成为所有继承该trait的类的父类

class MyUtil {

def printMessage(msg: String) =
println(msg)

}

trait Logger extends MyUtil {

def log(msg: String) =
printMessage("log: " + msg)

}

class Person(val name: String) extends
Logger {

def sayHello {

log("Hi, I'm "
+ name)

printMessage("Hi,
I'm " + name)

}

}

new Person("leo").sayHello

log: Hi, I'm leo

Hi, I'm leo

将函数赋值给变量

// Scala中的函数是一等公民,可以独立定义,独立存在,而且可以直接将函数作为值赋值给变量

// Scala的语法规定,将函数赋值给变量时,必须在函数后面加上空格和下划线

def sayHello(name: String) {
println("Hello, " + name) }

val sayHelloFunc = sayHello _

sayHelloFunc("leo")

匿名函数

// Scala中,函数也可以不需要命名,此时函数被称为匿名函数。

// 可以直接定义函数之后,将函数赋值给某个变量;也可以将直接定义的匿名函数传入其他函数之中

// Scala定义匿名函数的语法规则就是,(参数名: 参数类型) => 函数体

// 这种匿名函数的语法必须深刻理解和掌握,在spark的中有大量这样的语法,如果没有掌握,是看不懂spark源码的

val sayHelloFunc = (name: String) => println("Hello,
" + name)

sayHelloFunc("leo")

变量带返回类型:

val sayHelloFunc:String=>Unit = (name:
String) => println("Hello, " + name)

高阶函数

// Scala中,由于函数是一等公民,因此可以直接将某个函数传入其他函数,作为参数。这个功能是极其强大的,也是Java这种面向对象的编程语言所不具备的。

// 接收其他函数作为参数的函数,也被称作高阶函数(higher-order function)

val sayHelloFunc = (name: String) =>
println("Hello, " + name)

def greeting(func: (String) => Unit,
name: String) { func(name) }

greeting(sayHelloFunc, "leo")

Array(1, 2, 3, 4, 5).map((num: Int) =>
num * num)

// 高阶函数的另外一个功能是将函数作为返回值,即返回值就是一个函数,如下面根据不同的msg生成不同的函数

def getGreetingFunc(msg: String) = (name:
String) => println(msg + ", " + name)

var greetingFunc =
getGreetingFunc("hello")

greetingFunc("leo")

greetingFunc = getGreetingFunc("hi")

greetingFunc("leo")

高阶函数的类型推断

// 高阶函数可以自动推断出参数类型,而不需要写明类型;而且对于只有一个参数的函数,还可以省去其小括号;

def greeting(func: (String) => Unit,
name: String) { func(name) }

greeting((name: String) =>
println("Hello, " + name), "leo")

greeting((name) => println("Hello,
" + name), "leo")

greeting(name => println("Hello,
" + name), "leo")

// 只要某个参数只在函数体里出现一次,则可以使用下划线 _ 来替换这个参数

def triple(func: (Int) => Int) = {
func(3) }

triple(3 * _)

// 诸如3 * _的这种语法,必须掌握!!spark源码中大量使用了这种语法!

有多少个下划线,则就表示有多少个不同的参数。多个占位符时,第一个下划线表示第一个参数,第二个下划线表示第二个参数,以此类推;所以同一参数多处出现时是无法使用这种占位符来表示的。

使用占位符时,有时无法推导出类型,如:

scala> val f = _ + _

此时需明确写出类型:

scala> val f = (_: Int) + (_: Int)

f: (Int, Int) => Int = <function2>

Scala的常用高阶函数

// map: 对传入的每个元素都进行映射,返回一个处理后的元素

Array(1, 2, 3, 4, 5).map(2 * _)

// foreach: 对传入的每个元素都进行处理,但是没有返回值

(1 to 9).map("*" *
_).foreach(println _)

// filter: 对传入的每个元素都进行条件判断,如果对元素返回true,则保留该元素,否则过滤掉该元素

(1 to 20).filter(_ % 2 == 0)

// reduceLeft: 从左侧元素开始,进行reduce操作,即先对元素1和元素2进行处理,然后将结果与元素3处理,再将结果与元素4处理,依次类推,即为reduce;reduce操作必须掌握!spark编程的重点!!!

// 下面这个操作就相当于1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9

(1 to 9).reduceLeft( _ * _)

// sortWith: 对元素进行两两相比,进行排序

Array(3, 2, 5, 4, 10, 1).sortWith(_ < _)

闭包

// 闭包最简洁的解释:函数在变量不处于其有效作用域时,还能够对变量进行访问,即为闭包

def getGreetingFunc(msg: String) = (name:
String) => println(msg + ", " + name)

val greetingFuncHello = getGreetingFunc("hello")

val greetingFuncHi =
getGreetingFunc("hi")

// 两次调用getGreetingFunc函数,传入不同的msg,并创建不同的函数返回

// 然而,msg只是一个局部变量,却在getGreetingFunc执行完之后,还可以继续存在创建的函数之中;greetingFuncHello("leo"),调用时,值为"hello"的msg被保留在了函数体内部,可以反复的使用

// 这种变量超出了其作用域,还可以使用的情况,即为闭包

// Scala通过为每个函数创建对象来实现闭包,实际上对于getGreetingFunc函数创建的函数,msg是作为函数对象的变量存在的,因此每个函数才可以拥有不同的msg

SAM转换

如果Scala调用Java的某个方法传入的是一个SAM,则可以通过Scala提供的很方便的一种转换,将函数对象会传给Java方法

// 在Java中,由于不支持直接将函数传入一个方法作为参数,通常来说,唯一的办法就是定义一个实现了某个接口的类的实例对象,该对象只有一个方法,犹如这样接口只有单个的抽象方法,就叫single abstract method,简称为SAM

// 由于Scala是可以调用Java的代码的,因此当我们调用Java的某个方法时,可能就不得不创建SAM传递给方法,非常麻烦;但是Scala又是支持直接传递函数的。此时就可以使用Scala提供的,在调用Java方法时,使用Scala提供的SAM转换功能,即将SAM转换为Scala函数

// 要使用SAM转换,需要使用Scala提供的特性,隐式转换

import javax.swing._

import java.awt.event._

val button = new JButton("Click")

button.addActionListener(new ActionListener
{// ActionListener接口只有一个抽象方法,这样的接口叫SAM

override def actionPerformed(event:
ActionEvent) {

println("Click
Me!!!")

}

})

implicit def
getActionListener(actionProcessFunc: (ActionEvent) => Unit) = new
ActionListener {

override def actionPerformed(event:
ActionEvent) {

actionProcessFunc(event)

}

}

button.addActionListener((event:
ActionEvent) => println("Click Me!!!"))

Currying函数

// Curring函数,指的是,将原来接收两个参数的一个函数,转换为两个函数,第一个函数接收原先的第一个参数,然后返回接收原先第二个参数的第二个函数。

// 在函数调用的过程中,就变为了两个函数连续调用的形式

// 在Spark的源码中,也有体现,所以对()()这种形式的Curring函数,必须掌握!

def sum(a: Int, b: Int) = a + b

sum(1, 1)

def sum2(a: Int) = (b: Int) => a + b

sum2(1)(1)

def sum3(a: Int)(b: Int) = a + b

sum2(1)(1)

return到外层函数

// Scala中,不需要使用return来返回函数的值,函数最后一行语句的值,就是函数的返回值。在Scala中,return用于在匿名函数中返回值给包含匿名函数的带名函数(即外层函数),并作为带名函数的返回值。

// 使用return的匿名函数,是必须给出返回类型的,否则无法通过编译

def greeting(name: String) = {

def sayHello(name: String):String
= {

return "Hello,
" + name

}

sayHello(name)

}

greeting("leo")

Scala的集合体系结构

// Scala中的集合体系主要包括:Iterable、Seq、Set、Map。其中Iterable是所有集合trait的根trait。这个结构与Java的集合体系非常相似(最上层为public interface
Collection<E> extends Iterable<E>)。

// Scala中的集合是分成可变和不可变两类集合的,其中可变集合就是说,集合的元素可以动态修改,而不可变集合的元素在初始化之后,就无法修改了。分别对应scala.collection.mutable和scala.collection.immutable两个包。

// Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一个序列,通常可以使用“1 to 10”这种语法来产生一个Range。 ArrayBuffer就类似于Java中的ArrayList。

List

// List代表一个不可变的列表

// List的创建,val list = List(1, 2, 3, 4)

// List有head和tail,head代表List的第一个元素,tail代表第一个元素之后的所有元素,list.head,list.tail

// List有特殊的::操作符,可以用于将head和tail合并成一个List,0 :: list

// ::这种操作符要清楚,在spark源码中都是有体现的,一定要能够看懂!

// 如果一个List只有一个元素,那么它的head就是这个元素,它的tail是Nil

// 案例:用递归函数来给List中每个元素都加上指定前缀,并打印加上前缀的元素

def decorator(l: List[Int], prefix: String)
{

if (l != Nil) {

println(prefix + l.head)

decorator(l.tail,
prefix)

}

}

LinkedList

// LinkedList代表一个可变的列表,使用elem可以引用其头部,使用next可以引用其尾部

// val l = scala.collection.mutable.LinkedList(1,
2, 3, 4, 5); l.elem; l.next

// 案例:使用while循环将LinkedList中的每个元素都乘以2

val list =
scala.collection.mutable.LinkedList(1, 2, 3, 4, 5)

var currentList = list

while (currentList != Nil) {

currentList.elem = currentList.elem *
2

currentList = currentList.next

}

// 案例:使用while循环将LinkedList中,从第一个元素开始,每隔一个元素,乘以2

val list =
scala.collection.mutable.LinkedList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

var currentList = list

var first = true

while (currentList != Nil &&
currentList.next != Nil) {

if (first) { currentList.elem =
currentList.elem * 2; first = false }

currentList  =
currentList.next.next

if (currentList != Nil)
currentList.elem = currentList.elem * 2

}

Set

// Set代表一个没有重复元素的集合,Set为trait,分为可变与不可变两种trait

// 将重复元素加入Set是没有用的,比如val s = Set(1, 2, 3); s + 1; s + 4

// 而且Set是不保证插入顺序的,也就是说,Set中的元素是乱序的,val s = new scala.collection.mutable.HashSet[Int]();
s += 1; s += 2; s += 5

// LinkedHashSet会用一个链表维护插入顺序,val s = new
scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5

// SrotedSet会自动根据key来进行排序,val s =
scala.collection.mutable.SortedSet("orange", "apple",
"banana")

集合的函数式编程

// 集合的函数式编程非常非常非常之重要!!!

// 必须完全掌握和理解Scala的高阶函数是什么意思,Scala的集合类的map、flatMap、reduce、reduceLeft、foreach等这些函数,就是高阶函数,因为可以接收其他函数作为参数

// 高阶函数的使用,也是Scala与Java最大的一点不同!!!因为Java里面是没有函数式编程的,也肯定没有高阶函数,也肯定无法直接将函数传入一个方法,或者让一个方法返回一个函数

// 对Scala高阶函数的理解、掌握和使用,可以大大增强你的技术,而且也是Scala最有诱惑力、最有优势的一个功能!!!

// 此外,在Spark源码中,有大量的函数式编程,以及基于集合的高阶函数的使用!!!所以必须掌握,才能看懂spark源码

// map案例实战:为List中每个元素都添加一个前缀

List("Leo", "Jen",
"Peter", "Jack").map("name is " + _)

// faltMap案例实战:将List中的多行句子拆分成单词

List("Hello World", "You
Me").flatMap(_.split(" "))

// foreach案例实战:打印List中的每个单词

List("I", "have",
"a", "beautiful", "house").foreach(println(_))

// zip案例实战:对学生姓名和学生成绩进行关联

List("Leo", "Jen",
"Peter", "Jack").zip(List(100, 90, 75, 83))

函数式编程综合案例:统计多个文本内的单词总数

// 使用scala的io包将文本文件内的数据读取出来

val lines01 =
scala.io.Source.fromFile("C://Users//Administrator//Desktop//test01.txt").mkString

val lines02 =
scala.io.Source.fromFile("C://Users//Administrator//Desktop//test02.txt").mkString

// 使用List的伴生对象,将多个文件内的内容创建为一个List

val lines = List(lines01, lines02)

// 下面这一行才是我们的案例的核心和重点,因为有多个高阶函数的链式调用,以及大量下划线的使用,如果没有透彻掌握之前的课讲解的Scala函数式编程,那么下面这一行代码,完全可能会看不懂!!!

// 但是下面这行代码其实就是Scala编程的精髓所在,就是函数式编程,也是Scala相较于Java等编程语言最大的功能优势所在

// 而且,spark的源码中大量使用了这种复杂的链式调用的函数式编程

// 而且,spark本身提供的开发人员使用的编程api的风格,完全沿用了Scala的函数式编程,比如Spark自身的api中就提供了map、flatMap、reduce、foreach,以及更高级的reduceByKey、groupByKey等高阶函数

// 如果要使用Scala进行spark工程的开发,那么就必须掌握这种复杂的高阶函数的链式调用!!!

lines.flatMap(_.split("
")).map((_, 1)).map(_._2).reduceLeft(_ + _)

模式匹配

// Scala是没有Java中的switch case语法的,相对应的,Scala提供了更加强大的match case语法,即模式匹配,类替代switch case,match case也被称为模式匹配

// Scala的match case与Java的switch
case最大的不同点在于,Java的switch case仅能匹配变量的值,比1、2、3等;而Scala的match
case可以匹配各种情况,比如变量的类型、集合的元素、有值或无值

// match case的语法如下:变量 match { case 值 => 代码 }。如果值为下划线,则代表了不满足以上所有情况下的默认情况如何处理。此外,match case中,只要一个case分支满足并处理了,就不会继续判断下一个case分支了。(与Java不同,java的switch case需要用break阻止)

// match case语法最基本的应用,就是对变量的值进行模式匹配

// 案例:成绩评价

def judgeGrade(grade: String) {

grade match {

case "A" =>
println("Excellent")

case "B" =>
println("Good")

case "C" =>
println("Just so so")

case _ =>
println("you need work harder")

}

}

在模式匹配中使用if守卫

// Scala的模式匹配语法,有一个特点在于,可以在case后的条件判断中,不仅仅只是提供一个值,而是可以在值后面再加一个if守卫,进行双重过滤

// 案例:成绩评价(升级版)

def judgeGrade(name: String, grade:
String) {

grade match {

case "A" =>
println(name + ", you are excellent")

case "B" =>
println(name + ", you are good")

case "C" =>
println(name + ", you are just so so")

case _ if name ==
"leo" => println(name + ", you are a good boy, come on")

case _ =>
println("you need to work harder")

}

}

在模式匹配中进行变量赋值

// Scala的模式匹配语法,有一个特点在于,可以将模式匹配的默认情况,将下划线替换为一个变量名,此时模式匹配语法就会将要匹配的值赋值给这个变量,从而可以在后面的处理语句中使用要匹配的值

// 为什么有这种语法??思考一下。因为只要使用用case匹配到的值,是不是我们就知道这个只啦!!在这个case的处理语句中,是不是就直接可以使用写程序时就已知的值!

// 但是对于下划线_这种情况,所有不满足前面的case的值,都会进入_这种默认情况进行处理,此时如果我们在处理语句中需要拿到具体的值进行处理呢?那就需要使用这种在模式匹配中进行变量赋值的语法!!

// 案例:成绩评价(升级版)

def judgeGrade(name: String, grade: String)
{

grade match {

case "A" =>
println(name + ", you are excellent")

case "B" =>
println(name + ", you are good")

case "C" =>
println(name + ", you are just so so")

case grade_ if name == "leo" => println(name +
", you are a good boy, come on, your grade is " + grade+  "
: " + grade_)

case _ => println("you need to work harder, your
grade is " + grade)

}

}

对类型进行模式匹配

// Scala的模式匹配一个强大之处就在于,可以直接匹配类型,而不是值!!!这点是java的switch case绝对做不到的。

// 理论知识:对类型如何进行匹配?其他语法与匹配值其实是一样的,但是匹配类型的话,就是要用“case 变量:
类型 => 代码”这种语法,而不是匹配值的“case 值 => 代码”这种语法。

// 案例:异常处理

import java.io._

def processException(e: Exception) {

e match {

case e1:
IllegalArgumentException => println("you have illegal arguments!
exception is: " + e1)

case e2:
FileNotFoundException => println("cannot find the file you need read or
write!, exception is: " + e2)

case e3: IOException
=> println("you got an error while you were doing IO operation!
exception is: " + e3)

case _: Exception =>
println("cannot know which exception you have!" )

}

}

processException(new IOException
("File not found"))

对Array和List进行模式匹配

// 对Array进行模式匹配,分别可以匹配带有指定元素的数组、带有指定个数元素的数组、以某元素打头的数组

// 对List进行模式匹配,与Array类似,但是需要使用List特有的::操作符

// 案例:对朋友打招呼

def greeting(arr: Array[String])
{

arr match {

case
Array("Leo") => println("Hi, Leo!")

case Array(girl1, girl2,
girl3) => println("Hi, girls, nice to meet you. " + girl1 + "
and " + girl2 + " and " + girl3)

case
Array("Leo", _*) => println("Hi, Leo, please introduce your
friends to me.")

case _ =>
println("hey, who are you?")

}

}

greeting(Array("Leo","Jack"))

def greeting(list: List[String])
{

list match {

case "Leo" ::
Nil => println("Hi, Leo!")

case girl1 :: girl2 ::
girl3 :: Nil => println("Hi, girls, nice to meet you. " + girl1 +
" and " + girl2 + " and " + girl3)

case "Leo" ::
tail => println("Hi, Leo, please introduce your friends to me.")

case _ =>
println("hey, who are you?")

}

}

greeting(List("Leo","Jack"))

case class与模式匹配

// Scala中提供了一种特殊的类,用case class进行声明,中文也可以称作样例类。case class其实有点类似于Java中的JavaBean的概念。即只定义field,并且由Scala编译时自动提供getter和setter方法,但是没有method。

// case class的主构造函数接收的参数通常不需要使用var或val修饰,Scala自动就会使用val修饰(但是如果你自己使用var修饰,那么还是会按照var来)

//  Scala自动为case
class定义了伴生对象,也就是object,并且定义了apply()方法,该方法接收主构造函数中相同的参数,并返回case class对象

// 案例:学校门禁

class Person

case class Teacher(name: String, subject:
String) extends Person

case class Student(name: String, classroom:
String) extends Person

def judgeIdentify(p: Person) {

p match {

case Teacher(name,
subject) => println("Teacher, name is " + name + ", subject
is " + subject)

case Student(name,
classroom) => println("Student, name is " + name + ",
classroom is " + classroom)

case _ =>
println("Illegal access, please go out of the school!")

}

}

judgeIdentify(Student("Leo","1"))

Option与模式匹配

// Scala有一种特殊的类型,叫做Option。Option有两种值,一种是Some,表示有值,一种是None,表示没有值。

// Option通常会用于模式匹配中,用于判断某个变量是有值还是没有值,这比null来的更加简洁明了

// Option的用法必须掌握,因为Spark源码中大量地使用了Option,比如Some(a)、None这种语法,因此必须看得懂Option模式匹配,才能够读懂spark源码。

// 案例:成绩查询

val grades = Map("Leo" ->
"A", "Jack" -> "B", "Jen" ->
"C")

def getGrade(name: String) {

val grade = grades.get(name)

grade match {

case Some(grade1) =>
println("your grade is " + grade1)

case None =>
println("Sorry, your grade information is not in the system")

}

}

getGrade("Lily")

getGrade("Leo")

Scala集合类的某些标准操作会产生Option可选值,如Map的get方法,查到值时返回Some(value)对象,没查到时返回None对象(而Java中返回的为Null,这会容易导致程序运行错误)

类型参数

类型参数是什么?类型参数其实就类似于Java中的泛型。先说说Java中的泛型是什么,比如我们有List a = new ArrayList(),接着a.add(1),没问题,a.add("2"),然后我们a.get(1) == 2,对不对?肯定不对了,a.get(1)获取的其实是个String——"2",String——"2"怎么可能与一个Integer类型的2相等呢?

所以Java中提出了泛型的概念,其实也就是类型参数的概念,此时可以用泛型创建List,List a = new
ArrayList[Integer](),那么,此时a.add(1)没问题,而a.add("2")呢?就不行了,因为泛型会限制,只能往集合中添加Integer类型,这样就避免了上述的问题。

那么Scala的类型参数是什么?其实意思与Java的泛型是一样的,也是定义一种类型参数,比如在集合,在类,在函数中,定义类型参数,然后就可以保证使用到该类型参数的地方,就肯定,也只能是这种类型。从而实现程序更好的健壮性。

此外,类型参数是Spark源码中非常常见的,因此同样必须掌握,才能看懂spark源码。

泛型类

// 泛型类(类声明时类名后面中括号中的即为类型参数),顾名思义,其实就是在类的声明中,定义一些泛型类型,然后在类内部,比如field或者method,就可以使用这些泛型类型。

// 使用泛型类,通常是需要对类中的某些成员,比如某些field和method中的参数或变量,进行统一的类型限制,这样可以保证程序更好的健壮性和稳定性。

// 如果不使用泛型进行统一的类型限制,那么在后期程序运行过程中,难免会出现问题,比如传入了不希望的类型,导致程序出问题。

// 在使用类的时候,比如创建类的对象,将类型参数替换为实际的类型,即可。

案例:新生报到,每个学生来自不同的地方,id可能是Int,可能是String

class Student[T](val localId:
T) { // 在类参数中使用类型参数

def getSchoolId(hukouId: T) =
"S-" + hukouId + "-" + localId // 在方法参数中使用类型参数

}

val leo = new Student[Int](111)

// Scala自动推断泛型类型特性:直接给使用了泛型类型的field赋值时,Scala会自动进行类型推断。

scala> val leo = new Student(111)

leo: Student[Int]
= Student@f001896

scala> val leo = new
Student("string")

leo: Student[String]
= Student@488eb7f2

泛型函数

// 泛型函数(方法声明时方法名后面中括号中的即为类型参数),与泛型类类似,可以给某个函数在声明时指定泛型类型,然后在函数体内,多个变量或者返回值之间,就可以使用泛型类型进行声明,从而对某个特殊的变量,或者多个变量,进行强制性的类型限制。

案例:卡片售卖机,可以指定卡片的内容,内容可以是String类型或Int类型

def getCard[T](content:
T) = {

if(content.isInstanceOf[Int])
"card: 001, " + content

else
if(content.isInstanceOf[String]) "card: this is your card, " +
content

else "card: " + content

}

getCard[String]("hello
world")

getCard[Double](0.01)

// 与泛型类一样,你可以通过给使用了泛型类型的变量传递值来让Scala自动推断泛型的实际类型,也可以在调用函数时,手动指定泛型类型,上面就是在调用时手动在中括号中指定的,下面靠传入值自动推断:

scala> getCard ("hello world")

res2: String = card: this is your card,
hello world

scala> getCard (0.01)

res3: String = card: 0.01

上边界Bounds

// 在指定泛型类型的时候,有时,我们需要对泛型类型的范围进行界定,而不是可以是任意的类型。比如,我们可能要求某个泛型类型,它就必须是某个类的子类,这样在程序中就可以放心地调用泛型类型继承的父类的方法,程序才能正常的使用和运行。此时就可以使用上下边界Bounds的特性。如下面没有使用上边界时,是不能调用类型参数相关方法的:

class Person(val name: String) {

def makeFriends(p: Person) {}

}

class Party[T](p1: T, p2: T) {

def play = p1.makeFriends(p2) // 编译会出错

}

// Scala的上下边界特性允许泛型类型必须是某个类的子类,或者必须是某个类的父类

案例:在派对上交朋友

class Person(val name: String) {

def sayHello = println("Hello, I'm " + name)

def makeFriends(p: Person) {

sayHello

p.sayHello

}

}

class Student(name: String) extends Person(name)

class Party[T <: Person](p1: T, p2: T) { // <:要求T必须是Person或其子类,由于p1 类型为Person或其子类Student,所以可以调用父类中的方法

def play = p1.makeFriends(p2)

}

val leo = new Student("Leo")

val lily = new Student("Lily")

new Party(leo,lily).play

下边界Bounds

// 除了指定泛型类型的上边界,还可以指定下边界,即指定泛型类型必须是某个类的父类

案例:领身份证(只能是自己或父亲代领)

class Father(val name: String)

class Child(name: String) extends
Father(name)

def getIDCard[R >: Child](person: R) { // R必须是Child的父类

if (person.getClass ==
classOf[Child]) println("please tell us your parents' names.")

else if (person.getClass ==
classOf[Father]) println("sign your name for your child's id card.")

else println("sorry, you are
not allowed to get id card.")

}

val f = new Father("Father")

val c = new Child("Child")

scala> getIDCard[Father](f)

sign your name for your child's id card.

scala> getIDCard[Child](c)

please tell us your parents' names.

scala> getIDCard(f) // 类型自动推断

sign your name for your child's id card.

scala> getIDCard(c) // 类型自动推断

please tell us your parents' names.

val s = new Student("Student")

scala> getIDCard(s)

sorry, you are not allowed to get id
card.

注:下边界与上边界是不同的,上边界是为了可以调用类型参数相应方法,而下边界是为了限制泛型类或泛型函数只适用于哪些类,而不是为了调用类型参数相应方法。

View Bounds

// 上下边界Bounds,虽然可以让一种泛型类型,支持有父子关系的多种类型。但是,在某个类与上下边界Bounds指定的父子类型范围内的类都没有任何关系,则默认是肯定不能接受的。

// 然而,View Bounds作为一种上下边界Bounds的加强版,支持可以对类型进行隐式转换,将指定的类型进行隐式转换后,再判断是否在边界指定的类型范围内

案例:跟小狗交朋友

class Person(val name: String) {

def sayHello =
println("Hello, I'm " + name)

def makeFriends(p: Person) {

sayHello

p.sayHello

}

}

class Student(name: String) extends
Person(name)

class Dog(val name: String) { def
sayHello = println("Wang, Wang, I'm " + name) }

implicit def dog2person(o: Object):
Person =

if(o.isInstanceOf[Dog])
{println("-D-");val _o = o.asInstanceOf[Dog]; new Person(_o.name){

override
def sayHello = _o.sayHello

}
}// 如果是狗,隐式的转换为人

//注:即使是Person或Student,也要将Object强转成Person后返回,而不能直接将o返回,否则可能引起循环调用隐式转换

else
if(o.isInstanceOf[Person]) {println("-P-");val _o =
o.asInstanceOf[Person];_o} // 如果是人,不用转换,只是强转型后返回

else
{println("-O-");error(o.toString)} //其他情况返回Nothing

class Party[T <% Person](p1: T){ // <%表示T可以是Person或其子类,或者是可以经过隐式转换后成为Person或其子类的类

def play() =
{println(p1.name);println("--------------------")}

}

class Party2[T <% Person](p1: T,p2: T){

def play() = {p1.makeFriends(p2);println("--------------------")}

}

val leo = new Person("Leo")

val lily = new Student("Lily")

new Party(leo).play()

new Party(lily).play()

val dog = new Dog("Dog")

new Party(dog).play()//发生隐式转换

new Party2(lily,leo).play()

new Party2(dog,leo).play()//发生隐式转换

new Party2(lily,dog).play()//发生隐式转换

Context Bounds

// Context Bounds是一种特殊的Bounds,它会根据泛型类型的声明,比如“T: 类型”要求必须存在一个类型为“类型[T]”的隐式值(运行时Scala会帮我们自动注入这个已存在的隐式值)。其实个人认为,Context
Bounds之所以叫Context,是因为它基于的是一种全局的上下文,需要使用到上下文中的隐式值以及注入。

案例:使用Scala内置的比较器比较大小

// Ordering[T]类似Java中的Comparator<T>比较器

class Calculator[T: Ordering] (val number1: T, val number2: T) {

//运行时,Scala会在上下文中去找类型为Ordering[T]的隐式值并注进来,所以调用该方法时不需要传递该参数值了

def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0)
number1 else number2

}

new Calculator(3,4).max

Manifest Context Bounds

// 在Scala中,如果要实例化一个泛型数组,就必须使用Manifest Context Bounds。也就是说,如果数组元素类型为T的话,需要为类或者函数定义[T: Manifest]泛型类型,这样才能实例化Array[T]这种泛型数组。

案例:打包饭菜(一种食品打成一包)

class Meat(val name: String)

class Vegetable(val name: String)

def packageFood[T: Manifest] (food: T*) = {

// 创建泛型数组Array[T]:即数组中的元素类型要在运行时才能确定,编译时无法确定,元素类型为动态

val foodPackage = new Array[T](food.length)

for(i <- 0 until food.length) foodPackage(i) = food(i)

foodPackage

}

val gongbaojiding = new Meat("gongbaojiding")

val yuxiangrousi = new Meat("yuxiangrousi")

val shousiyangpai = new Meat("shousiyangpai")

val meatPackageFood =
packageFood(gongbaojiding,yuxiangrousi,shousiyangpai)

meatPackageFood: Array[Meat] = Array(Meat@20b829d5, Meat@7c5f29c6, Meat@4baf997)

val qingcai = new Vegetable("qingcai")

val baicai = new Vegetable("baicai")

val huanggua = new Vegetable("huanggua")

val vegPackageFood = packageFood(qingcai,baicai,huanggua)

vegPackageFood:
Array[Vegetable] = Array(Vegetable@583030bd,
Vegetable@2ac3d530, Vegetable@2431050d)

协变和逆变

// Scala的协变和逆变是非常有特色的!完全解决了Java中的泛型的一大缺憾!

// 举例来说,Java中,如果有Professional是Master的子类,那么Card[Professionnal]是不是Card[Master]的子类?答案是:不是。因此对于开发程序造成了很多的麻烦。

// 而Scala中,只要灵活使用协变和逆变,就可以解决Java泛型的问题。

案例:进入会场

class Master // 大师

class Professional extends Master // 专家,按理来说,大师是一种专家,应该是Master为Professional子类才对

// 大师以及专家的名片都可以进入会场

class Card[+T] (val name: String) // 协变:当类型B是类型A的子类型,则可以认为T[B]是T[A]的子类

def enterMeet(card: Card[Master]) {

println("welcome to have this
meeting!")

}

// 如果去掉+加号,则Card[Professional]不能传入到enterMeet方法

//专家级别及以上大师级别的名片就可以进入会场

class Card[-T] (val name: String) // 逆变:当类型B是类型A的子类型,则反过来可以认为T[A]是T[B]的子类型

// 要想父类也可以传进来,则要让Card进行逆变,这样Card[Master]就反过来成为Card[Professional]的子类,所以就能传进来了

def enterMeet(card: Card[Professional]) {

println("welcome to have this
meeting!")

}

Existential Type

// 在Scala里,有一种特殊的类型参数,就是Existential Type,存在性类型。这种类型务必掌握是什么意思,因为在spark源码实在是太常见了!

Array[T] forSome { type T }

Array[_]

scala> def foo[T](x : Array[T]) =
println(x.length)

foo: [T](x: Array[T])Unit

scala> 
foo(Array[String]("foo", "bar", "baz"))

3

scala> def foo(x : Array[T] forSome { type T}) = println(x.length)

foo: (x: Array[_])Unit

scala>
foo(Array[String]("foo", "bar", "baz"))

3

scala> def foo(x : Array[_]) = println(x.length)

foo: (x: Array[_])Unit

scala>
foo(Array[String]("foo", "bar", "baz"))

3

scala> def foo(x : Array[T] forSome {
type T <: CharSequence}) = x.foreach(y => println(y.length))

foo: (x: Array[_ <: CharSequence])Unit

scala>
foo(Array[String]("foo", "bar", "baz"))

3

3

3

隐式转换

Scala提供的隐式转换和隐式参数功能,是非常有特色的功能。是Java等编程语言所没有的功能。它可以允许你手动指定,将某种类型的对象转换成其他类型的对象。通过这些功能,可以实现非常强大,而且特殊的功能。

Scala的隐式转换,其实最核心的就是定义隐式转换函数,即implicit conversion function。定义的隐式转换函数,只要在编写的程序内引入,就会被Scala自动使用。Scala会根据隐式转换函数的签名,在程序中使用到隐式转换函数接收的参数类型定义的对象时,会自动将其传入隐式转换函数,转换为另外一种类型的对象并返回。这就是“隐式转换”。

隐式转换函数叫什么名字是无所谓的,因为通常不会由用户手动调用,而是由Scala进行调用。但是如果要使用隐式转换,则需要对隐式转换函数进行导入。因此通常建议将隐式转换函数的名称命名为“one2one”的形式。

Spark源码中有大量的隐式转换和隐式参数,因此必须精通这种语法。

// 要实现隐式转换,只要程序可见的范围内定义隐式转换函数即可。Scala会自动使用隐式转换函数。隐式转换函数与普通函数唯一的语法区别就是,要以implicit开头,而且最好要定义函数返回类型。

// 案例:特殊售票窗口(只接受特殊人群,比如学生、老人等)

class SpecialPerson(val name: String)

class Student(val name: String)

class Older(val name: String)

implicit def object2SpecialPerson
(obj: Object): SpecialPerson = {

if (obj.getClass ==
classOf[Student]) { val stu = obj.asInstanceOf[Student]; new
SpecialPerson(stu.name) }

else if (obj.getClass ==
classOf[Older]) { val older = obj.asInstanceOf[Older]; new
SpecialPerson(older.name) }

else error(obj.toString)

}

var ticketNumber = 0

def buySpecialTicket(p: SpecialPerson) = {//只针对特殊人群卖票,所以如果是学生与老人,则要先转换为特殊人群

ticketNumber += 1

"T-" + ticketNumber

}

class Teacher(val name:String)

scala> val tom = new
Teacher("tom")

scala> buySpecialTicket(tom)

java.lang.RuntimeException:
Teacher@277f7dd3

at
scala.sys.package$.error(package.scala:27)

at
scala.Predef$.error(Predef.scala:144)

at
.object2SpecialPerson(<console>:17)

... 32 elided

scala> val s = new
Student("student")

scala> val o = new Older("older")

scala> buySpecialTicket(s)

res1: String = T-1

scala> buySpecialTicket(o)

res2: String = T-2

使用隐式转换加强现有类型

// 隐式转换非常强大的一个功能,就是可以在不知不觉中加强现有类型的功能(有点像Java中的装饰模式)。也就是说,可以为某个类定义一个加强版的类,并定义互相之间的隐式转换,从而让源类在使用加强版的方法时,由Scala自动进行隐式转换为加强类,然后再调用该方法(如内置的Int 类的加强版 RichInt)。

// 案例:超人变身

class Man(val name: String)

class Superman(val name: String) {

def emitLaser = println("emit a
laster!") // 超人才有该方法

}

implicit def man2superman(man: Man): Superman = new Superman(man.name)

val leo = new Man("leo") // 普通人

leo.emitLaser // 调用不存在的方法时,会自动的先转换为超人

隐式转换函数作用域与导入

// Scala默认有两种找隐式转换的方式:首先是从源类型或者目标类型,这两类型的伴生对象中找隐式转换函数;然后在当前程序作用域内找隐式转换函数。

// 如果隐式转换函数不在上述两种情况下的话,那么就必须手动使用import语法引入某个包下的隐式转换函数,比如import test._。通常建议,仅仅在需要进行隐式转换的地方,比如某个函数或者方法内,用import导入隐式转换函数,这样可以缩小隐式转换函数的作用域,避免不需要的隐式转换。

隐式转换的发生时机

// 1、调用某个函数,但是给函数传入的参数的类型,与函数定义的接收参数类型不匹配(案例:特殊售票窗口

// 2、使用某个类型的对象,调用某个方法,而这个方法并不存在于该类型时(案例:超人变身

// 3、使用某个类型的对象,调用某个方法,虽然该类型有这个方法,但是传给方法的参数类型与方法定义的接收参数的类型不匹配(案例:特殊售票窗口加强版)

//4、将一种类型赋值给另一种类型时,如果类型不兼容时

// 案例:特殊售票窗口加强版

class TicketHouse {

var ticketNumber = 0

def buySpecialTicket(p:
SpecialPerson) = {

ticketNumber += 1

"T-" +
ticketNumber

}

}

new TicketHouse().buySpecialTicket(new Student("student"))

隐式参数

// 所谓的隐式参数,指的是在函数或者方法中,定义一个用implicit修饰的参数,此时Scala会尝试找到一个指定类型的,用implicit修饰的对象,即隐式值,并自动注入到该隐式参数。

// Scala会在两个范围内查找:一种是当前作用域内定义val或var隐式变量;一种是从隐式参数类型的伴生对象内找隐式值

// 案例:考试签到

class SignPen {

def write(content: String) =
println(content)

}

implicit val signPen = new SignPen

def signForExam(name: String) (implicit signPen: SignPen) {

signPen.write(name + " come to
exam in time.")

}

Actor

Scala的Actor类似于Java中的多线程编程。但是不同的是,Scala的Actor提供的模型与多线程有所不同。Scala的Actor尽可能地避免锁和共享状态,从而避免多线程并发时出现资源争用的情况,进而提升多线程编程的性能。此外,Scala Actor的这种模型还可以避免死锁等一系列传统多线程编程的问题。

Spark中使用的分布式多线程框架,是Akka。Akka也实现了类似Scala Actor的模型,其核心概念同样也是Actor。因此只要掌握了Scala Actor,那么在Spark源码研究时,至少即可看明白Akka Actor相关的代码。但是,换一句话说,由于Spark内部有大量的Akka Actor的使用,因此对于Scala Actor也至少必须掌握,这样才能学习Spark源码。

Actor的创建、启动和消息收发

// Scala提供了Actor trait来让我们更方便地进行actor多线程编程,就Actor trait就类似于Java中的Thread和Runnable一样,是基础的多线程基类和接口。我们只要重写Actor
trait的act方法,即可实现自己的线程执行体,与Java中重写run方法类似。

// 此外,使用start()方法启动actor;使用!符号,向actor发送消息;actor内部使用receive和模式匹配接收消息

// 案例:Actor Hello World

import scala.actors.Actor

class HelloActor extends Actor {

def act() {

while (true) {

receive {

case name: String => println("Hello, " + name)

}

}

}

}

val helloActor = new HelloActor

helloActor.start()

helloActor ! "leo"

收发case class类型的消息

// Scala的Actor模型与Java的多线程模型之间,很大的一个区别就是,Scala Actor天然支持线程之间的精准通信;即一个actor可以给其他actor直接发送消息。这个功能是非常强大和方便的。

// 要给一个actor发送消息,需要使用“actor ! 消息”的语法。在scala中,通常建议使用样例类,即case class来作为消息进行发送。然后在actor接收消息之后,可以使用scala强大的模式匹配功能来进行不同消息的处理。

// 案例:用户注册登录后台接口

import scala.actors.Actor

case class Login(username: String,
password: String)

case class Register(username: String,
password: String)

class UserManageActor extends Actor {

def act() {

while (true) {

receive {

case Login(username, password) => println("login, username is " +
username + ", password is " + password)

case Register(username, password) => println("register, username is
" + username + ", password is " + password)

}

}

}

}

val userManageActor = new UserManageActor

userManageActor.start()

userManageActor ! Register("leo",
"1234"); userManageActor ! Login("leo", "1234")

Actor之间互相收发消息

// 如果两个Actor之间要互相收发消息,那么scala的建议是,一个actor向另外一个actor发送消息时,同时带上自己的引用;其他actor收到自己的消息时,直接通过发送消息的actor的引用,即可以给它回复消息。

// 案例:打电话

import
scala.actors.Actor

case class
Message(content: String, sender: Actor)

class
LeoTelephoneActor extends Actor {

def act()
{

while (true) {

receive {

case Message(content, sender) => { println("leo telephone: " +
content); sender ! "I'm leo, please call me after 10 minutes." }

}

}

}

}

class
JackTelephoneActor(val leoTelephoneActor: Actor) extends Actor {

def act()
{

leoTelephoneActor ! Message("Hello, Leo, I'm Jack.", this)

receive {

case response: String => println("jack telephone: " + response)

}

}

}

val leoTel = new LeoTelephoneActor

val jackTel = new
JackTelephoneActor(leoTel)

leoTel.start

jackTel.start

同步消息和Future

// 默认情况下,消息都是异步的;但是如果希望发送的消息是同步的,即对方接受后,一定要给自己返回结果,那么可以使用!?的方式发送消息。即val reply = actor !? message。

// 如果要异步发送一个消息,但是在后续要获得消息的返回值,那么可以使用Future。即!!语法。val future = actor !! message。val reply = future()。

附件列表