Scala基礎(二)

匿名內部類
// 在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 =

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 extends Iterable)。
// Scala中的集合是分成可變和不可變兩類集合的,其中可變集合就是說,集合的元素可以動態修改,而不可變集合的元素在初始化之后,就無法修改了。分別對應scala.collection.mutable和scala.collection.immutable兩個包。
// Seq下包含了Range、ArrayBuffer、List等子trait。其中Range就代表了一個序列,通??梢允褂谩? 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 ArrayListInteger,那么,此時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比較器
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"))
scala> def foo(x : Array[T] forSome { type T}) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
scala> def foo(x : Array[_]) = println(x.length)
foo: (x: Array[_])Unit
scala> foo(Array[String]("foo", "bar", "baz"))
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"))

隱式轉換
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(: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()。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Scala與Java的關系 Scala與Java的關系是非常緊密的!! 因為Scala是基于Java虛擬機,也就是...
    燈火gg閱讀 3,479評論 1 24
  • 這篇主要講類、樣本類、模式匹配和特質的一些概念,其實這些概念在Java中有類似對應原型,所以理解起來不是很難。 1...
    Michaelhbjian閱讀 274評論 0 0
  • 函數式編程 引言 Scala中的函數是Java中完全沒有的概念。因為Java是完全面向對象的編程語言,沒有任何面向...
    義焃閱讀 1,289評論 2 5
  • 公司培訓第二日。 早起多時,但仍無法精準排除行程可出現的變故,時間感是路上不可遺棄之物。寧早起也不可遲到一秒。 我...
    高個矮子閱讀 258評論 0 0
  • 這是一場有計劃的親雪行動。 從清晨的家門口開始。興奮和期待伴著絲絲雪花飄落在心里。 如果消雪...
    大貓小記閱讀 615評論 1 2