Scala编程基础

ScalaJava的关系... 4java

安装Scala. 4算法

Scala解释器的使用... 4spring

声明变量... 5编程

数据类型与操做符... 5windows

函数调用与apply()函数... 5设计模式

if表达式... 6api

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

输入和输出... 6安全

循环... 7网络

高级for循环... 7

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

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

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

默认参数... 8

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

变长参数... 9

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

过程... 9

lazy... 9

异常... 10

Array. 10

ArrayBuffer. 11

遍历ArrayArrayBuffer. 11

数组常见操做... 11

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

算法案例:移除第一个负数以后的全部负数... 12

算法案例:移除第一个负数以后的全部负数(改良版)... 13

建立Map. 13

访问Map的元素... 13

修改Map的元素... 13

遍历Map. 14

SortedMapLinkedHashMap. 14

Map的元素类型—Tuple. 14

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

gettersetter. 15

自定义gettersetter. 15

仅暴露fieldgetter方法... 15

private[this]的使用... 16

Java风格的gettersetter方法... 16

辅助constructor. 16

constructor. 17

内部类... 18

object. 18

伴生对象... 19

object继承抽象类... 19

apply方法... 19

main方法... 20

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

extends. 21

overridesuper. 21

override field. 21

valvar override/实现 def. 23

isInstanceOfasInstanceOf. 24

getClassclassOf. 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

ArrayList进行模式匹配... 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

   

ScalaJava的关系

ScalaJava的关系是很是紧密的!!

由于Scala是基于Java虚拟机,也就是JVM的一门编程语言。全部Scala的代码,都须要通过编译为字节码,而后交由Java虚拟机来运行。

因此ScalaJava是能够无缝互操做的。Scala能够任意调用Java的代码。因此ScalaJava的关系是很是很是紧密的。

安装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,并且会显示结果的数据类型,好比IntDoubleString等等。

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

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

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

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

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

·例如,输入res2.to,敲击Tab键,解释器会显示出如下选项,toCharArraytoLowerCasetoStringtoUpperCase。由于此时没法断定你须要补全的是哪个,所以会提供给你全部的选项。

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

声明变量

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

·例如,val result = 1 + 1

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

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

·声明var变量:若是要声明值能够改变的引用,可使用var变量。

·例如,val myresult = 1myresult = 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

数据类型与操做符

·基本数据类型:ByteCharShortIntLongFloatDoubleBoolean

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

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

·类型的增强版类型scala使用不少增强类给数据类型增长了上百种加强的功能或函数。

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

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

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

·可是,在scala中,这些操做符实际上是数据类型的函数,好比1 + 1,能够写作1.+(1)

·例如,1.to(10),又能够写作1 to 10

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

函数调用与apply()函数

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

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

·不一样的一点是,若是调用函数时,不须要传递参数,则scala容许调用函数时省略括号的,例如,"Hello World".distinct

·apply函数

    ·Scala中的apply函数是很是特殊的一种函数,在Scalaobject中,能够声明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 objectapply()函数来建立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表达式是有值的,而ifelse子句的值类型可能不一样,此时if表达式的值是什么类型呢?Scala会自动进行推断,取两个类型的公共父类型。

    ·例如,if(age > 18) 1 else 0,表达式的类型是Int,由于10都是Int

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

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

·if语句放在多行中:默认状况下,REPL只能解释一行语句,可是if表达式一般须要放在多行。

    ·可使用{}的方式,好比如下方式,或者使用:pastectrl+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 }

输入和输出

·printprintlnprint打印时不会加换行符,而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.inScanner的做用。

·综合案例:游戏厅门禁

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循环:Scalawhile 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没有提供相似于javabreak语句。

    ·可是可使用boolean类型变量、return或者Breaksbreak函数来替代使用。

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中相似,也是长度不可改变的数组。此外,因为ScalaJava都是运行在JVM中,双方能够互相调用,所以Scala数组的底层其实是Java数组。例如字符串数组在底层就是JavaString[],整数数组在底层就是JavaInt[]

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

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)

// ArrayArrayBuffer能够互相进行转换

b.toArray

a.toBuffer

遍历ArrayArrayBuffer

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

// 使untilRichInt提供的函数

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

// 遍历mapentrySet

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

// 遍历mapkey

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

// 遍历mapvalue

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

// 生成新map,反转keyvalue

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

SortedMapLinkedHashMap

// SortedMap能够自动对Mapkey的排序

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) // 也能够不加括号,若是定义方法时不带括号,则调用方法时也不能带括号

gettersetter

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

2、而若是使用private修饰field,则只生成的gettersetter,且访问修饰也是private

3、若是定义val field,则只会生成getter方法

4 若是不但愿生成settergetter方法,则将field声明为private[this]

class Student {

  var name = "leo"

}

// 调用gettersetter方法,分别叫作namename_=

val leo = new Student

print(leo.name)

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

自定义gettersetter

// 若是只是但愿拥有简单的gettersetter方法,那么就按照scala提供的语法规则,根据需求为field选择合适的修饰符就好:varvalprivateprivate[this]

// 可是若是但愿可以本身对gettersetter进行控制,则能够自定义gettersetter方法

// 自定义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 方法

仅暴露fieldgetter方法

// 若是你不但愿fieldsetter方法,则能够定义为val,可是此时就不再能更改field的值了

// 可是若是但愿可以仅仅暴露出一个getter方法,而且还能经过某些方法更改field的值,那么须要综合使用private以及自定义getter方法。此时,因为fieldprivate的,因此settergetter都是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风格的gettersetter方法

// Scalagettersetter方法的命名与java是不一样的,是fieldfield_=的方式

// 若是要让scala自动生成java风格的gettersetter方法,只要给field添加@BeanProperty注解便可

// 此时会生成4个方法,name: Stringname_=(newValue: String): UnitgetName(): StringsetName(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)}//编译正确,证实没有使用varval修饰时,且在除主构造器中使用外,则使用private[this]来修饰

}

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

内部类

// 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

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

2、你能够将object看做是一个类class,只是这个类在内存中只有一个单例,且定义的object名就是实例名,不需咱们本身实例化,运行时JVM已帮咱们new出来了

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

4、注意,objectconstructor只会在其第一次被调用时执行一次,之后再次调用就不会再次执行constructor

5object一般用于做为单例模式的实现,或者放class的静态成员,好比工具方法

object Person {

  private var eyeNum = 2

  println("this Person object!")

  def getEyeNum = eyeNum

}

伴生对象

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

// 伴生类和伴生对象必须存放在一个.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方法中运行的代码,直接做为objectconstructor代码;并且用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 Traitscalac命令进行编译时,会把继承App Traitobjectconstructor代码都放到DelayedInit TraitdelayedInit方法中,而后由App Traitmain方法去调用执行

object来实现枚举功能

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

object Season extends Enumeration {

  val SPRING, SUMMER, AUTUMN, WINTER = Value

}

// 还能够经过Value传入枚举值的idname,经过idtoString能够获取; 还能够经过idname来查找枚举值

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关键字

// 继承就表明,子类能够从父类继承父类的fieldmethod;而后子类能够在本身内部放入父类所没有,子类特有的fieldmethod;使用继承能够有效复用代码

// 子类能够覆盖父类的fieldmethod;但要注意的是final类是不能被继承的,并且final类型的fieldmethod是没法被覆盖的

class Person {

  private var name = "leo"

  def getName = name

}

class Student extends Person {

  private var score = "A"

  def getScore = score

}

overridesuper

// 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 fieldgetter方法;只要在子类中使用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 = ""

}

valvar override/实现 def

abstract class Person {

    def id: Int  

}

class Student extends Person{

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

}

scala中定义了一个var变量,会自动生成gettersetter方法。因为父类中只定义了一个方法def id: Int,而子类中var变量会自动生成getterid)与setter方法(id_),可是父类并无这个setter方法,因此是没法重写的。以下修改便可:

abstractclass Person  {
def idInt    :   
def id_=Int//父类必须有set方法    (value: ) 
}
class Student extends Person{
overridevar9527//var变量自动生成getset方法      id =  
}

或者子类定义成val变量:

abstractclass Person  {
def idInt    :   
}
class Student extends Person{
overrideval9527      id = 
}
 
上面是valvar来实现def,下面是valvar来重写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重写valvar
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)
}
 
成员变量与方法之间重写与实现结论:可使用valvar来重写或实现def,也可使用def实现var;但不能使用def重写valvar,也不能使用def实现val

isInstanceOfasInstanceOf

// 若是咱们建立了子类的对象,可是又将其赋予了父类类型的变量。则在后续的程序中,咱们又须要将父类类型的变量转换为子类类型的变量,应该如何作?

// 首先,须要使用isInstanceOf判断对象是不是指定类的对象,若是是的话,则可使用asInstanceOf将对象转换为指定类型

// 注意,若是对象是null,则isInstanceOf必定返回falseasInstanceOf必定返回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

getClassclassOf

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

// 若是要求精确地判断对象就是指定类的对象,那么就只能使用getClassclassOf

// 对象.getClass能够精确获取对象所属的类classclassOf[]能够精确获取类,而后使用==操做符便可判断

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关键字来修饰fieldmethod,这样子类就能够继承这些成员或方法

// 还可使用protected[this],则只能在当前子类对象中访问父类的使用protected[this]修饰的fieldmethod,没法经过其余子类对象访问父类中的这些字段与方法

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后面指定须要调用父类哪一个构造器)

// 注意!若是是父类中接收的参数,好比nameage,子类中接收时,就不要用任何valvar来修饰了(或者带上修饰了,但将参数名命成不同也可),不然会认为是子类要覆盖父类的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会根据本身的规则,为varval类型的field生成对应的gettersetter方法,可是父类中是没有该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

}

除了经过上面直接覆盖父类的抽象成员外,还能够简接经过实现抽象成员所对应的gettersetter方法便可:

class B extends A{

         /*override*/ def a = "a" //因为是实现,因此能够省略override

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

}

上面是经过实现父类抽象成员所对应的gettersetter方法来重写抽象成员,因此能够看出:没有被初始化的成员所对应的gettersetter方法实质上就是抽象的,因此类要定义是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、父类的构造函数执行;2trait的构造代码执行,多个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不能定义辅助构造器,这是traitclass的惟一区别,可是若是需求就是要trait可以对field进行初始化,该怎么办呢?只能使用Scala中很是特殊的一种高级特性——提早定义

trait SayHello {

  val msg: String

  println("1SayHello")

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

}

class Person extends SayHello{

 println("2Person")

 val msg:String = "init"

}

new Person // NullPointerException异常,缘由父trait构造代码会先于子类构造器执行,在执行msg.toString时子类中的msg尚未来得及初始化。但若是将上面的val都修改成def,则能够正常运行。由于初始化父类时,因为子类实现(或重写,这里为实现)了msg方法,因此msg.toString会去调用子类实现的msg方法而返回"init",即便此时子类尚未被初始化:

trait SayHello {

  def msg: String

  println("1SayHello")

  println(msg.toString)

}

class Person extends SayHello{

 println("2Person")

 def msg:String = "init"

}

new Person

 

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

trait SayHello {

  val msg: String = "000"

  println("1SayHello")

  println(msg.toString) // NullPointerException

}

class Person extends SayHello{

 println("2Person")

 override val msg:String = "init"

}

new Person

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

trait SayHello {

  val msg: String = "000"

  println("1SayHello")

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

}

class Person extends SayHello{

 println("2Person")

}

new Person

 

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

如下也能够,缘由也是经过方法的多态来初始化:

trait SayHello {

  var msg: String

  println("1SayHello")

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

}

class Person extends SayHello{

 println("2Person")

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

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

}

new Person

 

trait SayHello {

  var msg: String

  println("1SayHello")

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

}

class Person extends SayHello{

 println("2Person")

 var msg:String = "init"

}

new Person

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

trait SayHello {

  val msg: String

  println("3SayHello")

  println(msg.toString)

}

class Person{println("2Person")}

val p = new {

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

} with Person with SayHello

1init -> 2Person -> 3SayHello

注意上面new … withclass …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("2SayHello")

  println(msg.toString)

}

class Person extends {

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

} with SayHello {

  println("3Person")

}

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处理,依次类推,即为reducereduce操做必须掌握!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中的集合体系主要包括:IterableSeqSetMap。其中Iterable是全部集合trait的根trait。这个结构与Java的集合体系很是类似(最上层为public interface Collection<E> extends Iterable<E>)。

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

// Seq下包含了RangeArrayBufferList等子trait。其中Range就表明了一个序列,一般可使用“1 to 10”这种语法来产生一个Range ArrayBuffer就相似于Java中的ArrayList

List

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

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

// Listheadtailhead表明List的第一个元素,tail表明第一个元素以后的全部元素,list.headlist.tail

// List有特殊的::操做符,能够用于将headtail合并成一个List0 :: list

// ::这种操做符要清楚,在spark源码中都是有体现的,必定要可以看懂!

// 若是一个List只有一个元素,那么它的head就是这个元素,它的tailNil

// 案例:用递归函数来给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表明一个没有重复元素的集合,Settrait,分为可变与不可变两种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的集合类的mapflatMapreducereduceLeftforeach等这些函数,就是高阶函数,由于能够接收其余函数做为参数

// 高阶函数的使用,也是ScalaJava最大的一点不一样!!!由于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))

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

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

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中就提供了mapflatMapreduceforeach,以及更高级的reduceByKeygroupByKey等高阶函数

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

 

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

模式匹配

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

 

// Scalamatch caseJavaswitch case最大的不一样点在于,Javaswitch case仅能匹配变量的值,比123等;而Scalamatch case能够匹配各类状况,好比变量的类型、集合的元素、有值或无值

 

// match case的语法以下:变量 match { case => 代码 }。若是值为下划线,则表明了不知足以上全部状况下的默认状况如何处理。此外,match case中,只要一个case分支知足并处理了,就不会继续判断下一个case分支了。(与Java不一样,javaswitch 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的模式匹配一个强大之处就在于,能够直接匹配类型,而不是值!!!这点是javaswitch 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"))

ArrayList进行模式匹配

// 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编译时自动提供gettersetter方法,可是没有method

 

// case class的主构造函数接收的参数一般不须要使用varval修饰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有一种特殊的类型,叫作OptionOption有两种值,一种是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可选值,如Mapget方法,查到值时返回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中提出了泛型的概念,其实也就是类型参数的概念,此时能够用泛型建立ListList a = new ArrayList[Integer](),那么,此时a.add(1)没问题,而a.add("2")呢?就不行了,由于泛型会限制,只能往集合中添加Integer类型,这样就避免了上述的问题。

 

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

 

此外,类型参数是Spark源码中很是常见的,所以一样必须掌握,才能看懂spark源码。

泛型类

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

// 使用泛型类,一般是须要对类中的某些成员,好比某些fieldmethod中的参数或变量,进行统一的类型限制,这样能够保证程序更好的健壮性和稳定性。

// 若是不使用泛型进行统一的类型限制,那么在后期程序运行过程当中,不免会出现问题,好比传入了不但愿的类型,致使程序出问题。

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

案例:新生报到,每一个学生来自不一样的地方,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

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

       //注:即便是PersonStudent,也要将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中,若是有ProfessionalMaster的子类,那么Card[Professionnal]是否是Card[Master]的子类?答案是:不是。所以对于开发程序形成了不少的麻烦。

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

 

案例:进入会场

class Master // 大师

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

 

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

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会在两个范围内查找:一种是当前做用域内定义valvar隐式变量;一种是从隐式参数类型的伴生对象内找隐式值

 

// 案例:考试签到

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

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

 

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

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

// Scala提供了Actor trait来让咱们更方便地进行actor多线程编程,就Actor trait就相似于Java中的ThreadRunnable同样,是基础的多线程基类和接口。咱们只要重写Actor traitact方法,便可实现本身的线程执行体,与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类型的消息

// ScalaActor模型与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 !! messageval reply = future()

 

 

附件列表

相关文章
相关标签/搜索