函數(shù)式編程
引言
Scala中的函數(shù)是Java中完全沒有的概念棍鳖。因?yàn)镴ava是完全面向?qū)ο蟮木幊陶Z言炮叶,沒有任何面向過程編程語言的特性,因此Java中的一等公民是類和對(duì)象鹊杖,而且只有方法的概念悴灵,即寄存和依賴于類和對(duì)象中的方法。Java中的方法是絕對(duì)不可能脫離類和對(duì)象獨(dú)立存在的骂蓖。
而Scala是一門既面向?qū)ο蠡鳎置嫦蜻^程的語言。因此在Scala中有非常好的面向?qū)ο蟮奶匦缘窍拢梢允褂肧cala來基于面向?qū)ο蟮乃枷腴_發(fā)大型復(fù)雜的系統(tǒng)和工程茫孔;而且Scala也面向過程,因此Scala中有函數(shù)的概念被芳。在Scala中缰贝,函數(shù)與類、對(duì)象等一樣畔濒,都是一等公民剩晴。Scala中的函數(shù)可以獨(dú)立存在,不需要依賴任何類和對(duì)象侵状。
Scala的函數(shù)式編程赞弥,就是Scala面向過程的最好的佐證。也正是因?yàn)楹瘮?shù)式編程趣兄,才讓Scala具備了Java所不具備的更強(qiáng)大的功能和特性绽左。
而之所以Scala一直沒有替代Java,是因?yàn)镾cala之前一直沒有開發(fā)過太多知名的應(yīng)用艇潭;而Java則不一樣拼窥,Java誕生的非常早戏蔑,上個(gè)世界90年代就誕生了,基于Java開發(fā)了大量知名的工程鲁纠。而且最重要的一點(diǎn)在于总棵,Java現(xiàn)在不只是一門編程語言,還是一個(gè)龐大的房交,涵蓋了軟件開發(fā)彻舰,甚至大數(shù)據(jù)、云計(jì)算的技術(shù)生態(tài)候味,Java生態(tài)中的重要框架和系統(tǒng)就太多了:Spring、Lucene隔心、Activiti白群、Hadoop等等。
將函數(shù)賦值給變量
// Scala中的函數(shù)是一等公民硬霍,可以獨(dú)立定義帜慢,獨(dú)立存在,而且可以直接將函數(shù)作為值賦值給變量
// Scala的語法規(guī)定唯卖,將函數(shù)賦值給變量時(shí)粱玲,必須在函數(shù)后面加上空格和下劃線
def sayHello(name: String) { println("Hello, " + name) }
val sayHelloFunc = sayHello _
sayHelloFunc("leo")
匿名函數(shù)
// Scala中,函數(shù)也可以不需要命名拜轨,此時(shí)函數(shù)被稱為匿名函數(shù)抽减。// 可以直接定義函數(shù)之后,將函數(shù)賦值給某個(gè)變量橄碾;也可以將直接定義的匿名函數(shù)傳入其他函數(shù)之中
// Scala定義匿名函數(shù)的語法規(guī)則就是卵沉,(參數(shù)名: 參數(shù)類型) => 函數(shù)體
// 這種匿名函數(shù)的語法必須深刻理解和掌握,在spark的中有大量這樣的語法法牲,如果沒有掌握史汗,是看不懂spark源碼的
val sayHelloFunc = (name: String) => println("Hello, " + name)
高階函數(shù)
// Scala中,由于函數(shù)是一等公民拒垃,因此可以直接將某個(gè)函數(shù)傳入其他函數(shù)停撞,作為參數(shù)。這個(gè)功能是極其強(qiáng)大的悼瓮,也是Java這種面向?qū)ο蟮木幊陶Z言所不具備的戈毒。
// 接收其他函數(shù)作為參數(shù)的函數(shù),也被稱作高階函數(shù)(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)
// 高階函數(shù)的另外一個(gè)功能是將函數(shù)作為返回值
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFunc = getGreetingFunc("hello")
greetingFunc("leo")
高階函數(shù)的類型推斷
// 高階函數(shù)可以自動(dòng)推斷出參數(shù)類型谤牡,而不需要寫明類型副硅;而且對(duì)于只有一個(gè)參數(shù)的函數(shù),還可以省去其小括號(hào)翅萤;如果僅有的一個(gè)參數(shù)在右側(cè)的函數(shù)體內(nèi)只使用一次恐疲,則還可以將接收參數(shù)省略腊满,并且將參數(shù)用_來替代
// 諸如3 * _的這種語法,必須掌握E嗉骸碳蛋!spark源碼中大量使用了這種語法!
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 * _)
Scala的常用高階函數(shù)
// map: 對(duì)傳入的每個(gè)元素都進(jìn)行映射省咨,返回一個(gè)處理后的元素
Array(1, 2, 3, 4, 5).map(2 * _)
// foreach: 對(duì)傳入的每個(gè)元素都進(jìn)行處理肃弟,但是沒有返回值
(1 to 9).map("*" * _).foreach(println _)
// filter: 對(duì)傳入的每個(gè)元素都進(jìn)行條件判斷,如果對(duì)元素返回true零蓉,則保留該元素笤受,否則過濾掉該元素
(1 to 20).filter(_ % 2 == 0)
// reduceLeft: 從左側(cè)元素開始,進(jìn)行reduce操作敌蜂,即先對(duì)元素1和元素2進(jìn)行處理箩兽,然后將結(jié)果與元素3處理,再將結(jié)果與元素4處理章喉,依次類推汗贫,即為reduce;reduce操作必須掌握秸脱!spark編程的重點(diǎn)B浒!摊唇!
// 下面這個(gè)操作就相當(dāng)于1 * 2 * 3 * 4 * 5 * 6 * 7 * 8 * 9
(1 to 9).reduceLeft( _ * _)
// sortWith: 對(duì)元素進(jìn)行兩兩相比咐蝇,進(jìn)行排序
閉包
// 閉包最簡潔的解釋:函數(shù)在變量不處于其有效作用域時(shí),還能夠?qū)ψ兞窟M(jìn)行訪問遏片,即為閉包
def getGreetingFunc(msg: String) = (name: String) => println(msg + ", " + name)
val greetingFuncHello = getGreetingFunc("hello")
val greetingFuncHi = getGreetingFunc("hi")
// 兩次調(diào)用getGreetingFunc函數(shù)嘹害,傳入不同的msg,并創(chuàng)建不同的函數(shù)返回
// 然而吮便,msg只是一個(gè)局部變量笔呀,卻在getGreetingFunc執(zhí)行完之后,還可以繼續(xù)存在創(chuàng)建的函數(shù)之中髓需;greetingFuncHello("leo")许师,調(diào)用時(shí),值為"hello"的msg被保留在了函數(shù)體內(nèi)部僚匆,可以反復(fù)的使用
// 這種變量超出了其作用域微渠,還可以使用的情況埠帕,即為閉包
// Scala通過為每個(gè)函數(shù)創(chuàng)建對(duì)象來實(shí)現(xiàn)閉包叛赚,實(shí)際上對(duì)于getGreetingFunc函數(shù)創(chuàng)建的函數(shù)坚芜,msg是作為函數(shù)對(duì)象的變量存在的式撼,因此每個(gè)函數(shù)才可以擁有不同的msg
// Scala編譯器會(huì)確保上述閉包機(jī)制
SAM轉(zhuǎn)換
// 在Java中,不支持直接將函數(shù)傳入一個(gè)方法作為參數(shù)娃闲,通常來說厘擂,唯一的辦法就是定義一個(gè)實(shí)現(xiàn)了某個(gè)接口的類的實(shí)例對(duì)象程腹,該對(duì)象只有一個(gè)方法;而這些接口都只有單個(gè)的抽象方法舅逸,也就是single abstract method桌肴,簡稱為SAM
// 由于Scala是可以調(diào)用Java的代碼的,因此當(dāng)我們調(diào)用Java的某個(gè)方法時(shí)琉历,可能就不得不創(chuàng)建SAM傳遞給方法坠七,非常麻煩;但是Scala又是支持直接傳遞函數(shù)的旗笔。此時(shí)就可以使用Scala提供的彪置,在調(diào)用Java方法時(shí),使用的功能蝇恶,SAM轉(zhuǎn)換悉稠,即將SAM轉(zhuǎn)換為Scala函數(shù)
// 要使用SAM轉(zhuǎn)換,需要使用Scala提供的特性艘包,隱式轉(zhuǎn)換
SAM轉(zhuǎn)換
import javax.swing._
import java.awt.event._
val button = new JButton("Click")
button.addActionListener(new ActionListener {
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函數(shù)
// Curring函數(shù),指的是耀盗,將原來接收兩個(gè)參數(shù)的一個(gè)函數(shù)想虎,轉(zhuǎn)換為兩個(gè)函數(shù),第一個(gè)函數(shù)接收原先的第一個(gè)參數(shù)叛拷,然后返回接收原先第二個(gè)參數(shù)的第二個(gè)函數(shù)舌厨。
// 在函數(shù)調(diào)用的過程中,就變?yōu)榱藘蓚€(gè)函數(shù)連續(xù)調(diào)用的形式
// 在Spark的源碼中忿薇,也有體現(xiàn)裙椭,所以對(duì)()()這種形式的Curring函數(shù),必須掌握署浩!
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
return
// Scala中揉燃,不需要使用return來返回函數(shù)的值,函數(shù)最后一行語句的值筋栋,就是函數(shù)的返回值炊汤。在Scala中,return用于在匿名函數(shù)中返回值給包含匿名函數(shù)的帶名函數(shù)弊攘,并作為帶名函數(shù)的返回值抢腐。
// 使用return的匿名函數(shù),是必須給出返回類型的襟交,否則無法通過編譯
def greeting(name: String) = {
def sayHello(name: String):String = {
return "Hello, " + name
}
sayHello(name)
}
函數(shù)式編程之集合操作
Scala的集合體系結(jié)構(gòu)
// Scala中的集合體系主要包括:Iterable迈倍、Seq、Set捣域、Map啼染。其中Iterable是所有集合trait的根trai宴合。這個(gè)結(jié)構(gòu)與Java的集合體系非常相似。
// Scala中的集合是分成可變和不可變兩類集合的提完,其中可變集合就是說形纺,集合的元素可以動(dòng)態(tài)修改,而不可變集合的元素在初始化之后徒欣,就無法修改了逐样。分別對(duì)應(yīng)scala.collection.mutable和scala.collection.immutable兩個(gè)包。
// Seq下包含了Range打肝、ArrayBuffer脂新、List等子trait。其中Range就代表了一個(gè)序列粗梭,通痴悖可以使用“1 to 10”這種語法來產(chǎn)生一個(gè)Range。 ArrayBuffer就類似于Java中的ArrayList断医。
List
// List代表一個(gè)不可變的列表
// List的創(chuàng)建滞乙,val list = List(1, 2, 3, 4)
// List有head和tail,head代表List的第一個(gè)元素鉴嗤,tail代表第一個(gè)元素之后的所有元素斩启,list.head,list.tail
// List有特殊的::操作符醉锅,可以用于將head和tail合并成一個(gè)List兔簇,0 :: list
// ::這種操作符要清楚,在spark源碼中都是有體現(xiàn)的硬耍,一定要能夠看懂垄琐!
// 如果一個(gè)List只有一個(gè)元素,那么它的head就是這個(gè)元素经柴,它的tail是Nil
// 案例:用遞歸函數(shù)來給List中每個(gè)元素都加上指定前綴狸窘,并打印加上前綴的元素
def decorator(l: List[Int], prefix: String) {
if (l != Nil) {
println(prefix + l.head)
decorator(l.tail, prefix)
}
}
LinkedList
// LinkedList代表一個(gè)可變的列表,使用elem可以引用其頭部口锭,使用next可以引用其尾部
// val l = scala.collection.mutable.LinkedList(1, 2, 3, 4, 5); l.elem; l.next
// 案例:使用while循環(huán)將LinkedList中的每個(gè)元素都乘以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循環(huán)將LinkedList中朦前,從第一個(gè)元素開始,每隔一個(gè)元素鹃操,乘以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代表一個(gè)沒有重復(fù)元素的集合
// 將重復(fù)元素加入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會(huì)用一個(gè)鏈表維護(hù)插入順序,
val s = new scala.collection.mutable.LinkedHashSet[Int](); i += 1; s += 2; s += 5
// SrotedSet會(huì)自動(dòng)根據(jù)key來進(jìn)行排序椰拒,
val s = scala.collection.mutable.SortedSet("orange", "apple", "banana")
集合的函數(shù)式編程
// 集合的函數(shù)式編程非常非常非常之重要>;思浴!
// 必須完全掌握和理解Scala的高階函數(shù)是什么意思褒脯,Scala的集合類的map便瑟、flatMap、reduce番川、reduceLeft到涂、foreach等這些函數(shù),就是高階函數(shù)颁督,因?yàn)榭梢越邮掌渌瘮?shù)作為參數(shù)
// 高階函數(shù)的使用践啄,也是Scala與Java最大的一點(diǎn)不同!3劣屿讽!因?yàn)镴ava里面是沒有函數(shù)式編程的,也肯定沒有高階函數(shù)吠裆,也肯定無法直接將函數(shù)傳入一個(gè)方法伐谈,或者讓一個(gè)方法返回一個(gè)函數(shù)
// 對(duì)Scala高階函數(shù)的理解、掌握和使用试疙,可以大大增強(qiáng)你的技術(shù)衩婚,而且也是Scala最有誘惑力、最有優(yōu)勢的一個(gè)功能PО摺!柱徙!
// 此外缓屠,在Spark源碼中,有大量的函數(shù)式編程护侮,以及基于集合的高階函數(shù)的使用5型辍!羊初!所以必須掌握滨溉,才能看懂spark源碼
// map案例實(shí)戰(zhàn):為List中每個(gè)元素都添加一個(gè)前綴
List("Leo", "Jen", "Peter", "Jack").map("name is " + _)
// faltMap案例實(shí)戰(zhàn):將List中的多行句子拆分成單詞
List("Hello World", "You Me").flatMap(_.split(" "))
// foreach案例實(shí)戰(zhàn):打印List中的每個(gè)單詞
List("I", "have", "a", "beautiful", "house").foreach(println(_))
// zip案例實(shí)戰(zhàn):對(duì)學(xué)生姓名和學(xué)生成績進(jìn)行關(guān)聯(lián)
List("Leo", "Jen", "Peter", "Jack").zip(List(100, 90, 75, 83))
函數(shù)式編程綜合案例:統(tǒng)計(jì)多個(gè)文本內(nèi)的單詞總數(shù)
// 使用scala的io包將文本文件內(nèi)的數(shù)據(jù)讀取出來
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的伴生對(duì)象,將多個(gè)文件內(nèi)的內(nèi)容創(chuàng)建為一個(gè)List
val lines = List(lines01, lines02)
// 下面這一行才是我們的案例的核心和重點(diǎn)长赞,因?yàn)橛卸鄠€(gè)高階函數(shù)的鏈?zhǔn)秸{(diào)用晦攒,以及大量下劃線的使用,如果沒有透徹掌握之前的課講解的Scala函數(shù)式編程得哆,那么下面這一行代碼脯颜,完全可能會(huì)看不懂!7肪荨栋操!
// 但是下面這行代碼其實(shí)就是Scala編程的精髓所在闸餐,就是函數(shù)式編程,也是Scala相較于Java等編程語言最大的功能優(yōu)勢所在
// 而且矾芙,spark的源碼中大量使用了這種復(fù)雜的鏈?zhǔn)秸{(diào)用的函數(shù)式編程
// 而且舍沙,spark本身提供的開發(fā)人員使用的編程api的風(fēng)格,完全沿用了Scala的函數(shù)式編程剔宪,比如Spark自身的api中就提供了map拂铡、flatMap、reduce歼跟、foreach和媳,以及更高級(jí)的reduceByKey、groupByKey等高階函數(shù)
// 如果要使用Scala進(jìn)行spark工程的開發(fā)哈街,那么就必須掌握這種復(fù)雜的高階函數(shù)的鏈?zhǔn)秸{(diào)用A敉!骚秦!
lines.flatMap(.split(" ")).map((, 1)).map(_.2).reduceLeft( + _)
模式匹配
引言
模式匹配是Scala中非常有特色她倘,非常強(qiáng)大的一種功能。模式匹配作箍,其實(shí)類似于Java中的swich case語法硬梁,即對(duì)一個(gè)值進(jìn)行條件判斷,然后針對(duì)不同的條件胞得,進(jìn)行不同的處理荧止。
但是Scala的模式匹配的功能比Java的swich case語法的功能要強(qiáng)大地多,Java的swich case語法只能對(duì)值進(jìn)行匹配阶剑。但是Scala的模式匹配除了可以對(duì)值進(jìn)行匹配之外跃巡,還可以對(duì)類型進(jìn)行匹配、對(duì)Array和List的元素情況進(jìn)行匹配牧愁、對(duì)case class進(jìn)行匹配素邪、甚至對(duì)有值或沒值(Option)進(jìn)行匹配。
而且對(duì)于Spark來說猪半,Scala的模式匹配功能也是極其重要的兔朦,在spark源碼中大量地使用了模式匹配功能。因此為了更好地編寫Scala程序磨确,并且更加通暢地看懂Spark的源碼沽甥,學(xué)好模式匹配都是非常重要的。
模式匹配
// Scala是沒有Java中的switch case語法的乏奥,相對(duì)應(yīng)的安接,Scala提供了更加強(qiáng)大的match case語法,即模式匹配,類替代switch case盏檐,match case也被稱為模式匹配
// Scala的match case與Java的switch case最大的不同點(diǎn)在于歇式,Java的switch case僅能匹配變量的值,比1胡野、2材失、3等;而Scala的match case可以匹配各種情況硫豆,比如變量的類型龙巨、集合的元素、有值或無值
// match case的語法如下:變量 match { case 值 => 代碼 }熊响。如果值為下劃線旨别,則代表了不滿足以上所有情況下的默認(rèn)情況如何處理。此外汗茄,match case中秸弛,只要一個(gè)case分支滿足并處理了,就不會(huì)繼續(xù)判斷下一個(gè)case分支了洪碳。(與Java不同递览,java的switch case需要用break阻止)
// match case語法最基本的應(yīng)用,就是對(duì)變量的值進(jìn)行模式匹配
// 案例:成績?cè)u(píng)價(jià)
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守衛(wèi)
// Scala的模式匹配語法瞳腌,有一個(gè)特點(diǎn)在于绞铃,可以在case后的條件判斷中,不僅僅只是提供一個(gè)值嫂侍,而是可以在值后面再加一個(gè)if守衛(wèi)儿捧,進(jìn)行雙重過濾
// 案例:成績?cè)u(píng)價(jià)(升級(jí)版)
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")
}
}
在模式匹配中進(jìn)行變量賦值
// Scala的模式匹配語法,有一個(gè)特點(diǎn)在于挑宠,可以將模式匹配的默認(rèn)情況纯命,下劃線,替換為一個(gè)變量名痹栖,此時(shí)模式匹配語法就會(huì)將要匹配的值賦值給這個(gè)變量,從而可以在后面的處理語句中使用要匹配的值
// 為什么有這種語法瞭空?揪阿?思考一下。因?yàn)橹灰褂糜胏ase匹配到的值咆畏,是不是我們就知道這個(gè)只啦D衔妗!在這個(gè)case的處理語句中旧找,是不是就直接可以使用寫程序時(shí)就已知的值溺健!
// 但是對(duì)于下劃線這種情況,所有不滿足前面的case的值钮蛛,都會(huì)進(jìn)入這種默認(rèn)情況進(jìn)行處理鞭缭,此時(shí)如果我們?cè)谔幚碚Z句中需要拿到具體的值進(jìn)行處理呢剖膳?那就需要使用這種在模式匹配中進(jìn)行變量賦值的語法!岭辣!
// 案例:成績?cè)u(píng)價(jià)(升級(jí)版)
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)
case _grade => println("you need to work harder, your grade is " + _grade)
}
}
對(duì)類型進(jìn)行模式匹配
// Scala的模式匹配一個(gè)強(qiáng)大之處就在于吱晒,可以直接匹配類型,而不是值B偻B乇簟!這點(diǎn)是java的switch case絕對(duì)做不到的偷遗。
// 理論知識(shí):對(duì)類型如何進(jìn)行匹配墩瞳?其他語法與匹配值其實(shí)是一樣的,但是匹配類型的話氏豌,就是要用“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!" )
}
}
對(duì)Array和List進(jìn)行模式匹配
// 對(duì)Array進(jìn)行模式匹配箩溃,分別可以匹配帶有指定元素的數(shù)組瞭吃、帶有指定個(gè)數(shù)元素的數(shù)組、以某元素打頭的數(shù)組
// 對(duì)List進(jìn)行模式匹配涣旨,與Array類似歪架,但是需要使用List特有的::操作符
// 案例:對(duì)朋友打招呼
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?")
}
}
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?")
}
}
case class與模式匹配
// Scala中提供了一種特殊的類,用case class進(jìn)行聲明霹陡,中文也可以稱作樣例類和蚪。case class其實(shí)有點(diǎn)類似于Java中的JavaBean的概念。即只定義field烹棉,并且由Scala編譯時(shí)自動(dòng)提供getter和setter方法攒霹,但是沒有method。
// case class的主構(gòu)造函數(shù)接收的參數(shù)通常不需要使用var或val修飾浆洗,Scala自動(dòng)就會(huì)使用val修飾(但是如果你自己使用var修飾催束,那么還是會(huì)按照var來)
// Scala自動(dòng)為case class定義了伴生對(duì)象,也就是object伏社,并且定義了apply()方法抠刺,該方法接收主構(gòu)造函數(shù)中相同的參數(shù),并返回case class對(duì)象
// 案例:學(xué)校門禁
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!")
}
}
Option與模式匹配
// Scala有一種特殊的類型摘昌,叫做Option速妖。Option有兩種值,一種是Some聪黎,表示有值罕容,一種是None,表示沒有值。
// Option通常會(huì)用于模式匹配中锦秒,用于判斷某個(gè)變量是有值還是沒有值露泊,這比null來的更加簡潔明了
// Option的用法必須掌握,因?yàn)镾park源碼中大量地使用了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(grade) => println("your grade is " + grade)
case None => println("Sorry, your grade information is not in the system")
}
}
類型參數(shù)
引言
類型參數(shù)是什么?類型參數(shù)其實(shí)就類似于Java中的泛型汇歹。先說說Java中的泛型是什么屁擅,比如我們有List a = new ArrayList(),接著a.add(1)产弹,沒問題派歌,a.add("2"),然后我們a.get(1) == 2痰哨,對(duì)不對(duì)胶果?肯定不對(duì)了,a.get(1)獲取的其實(shí)是個(gè)String——"2"斤斧,String——"2"怎么可能與一個(gè)Integer類型的2相等呢早抠?
所以Java中提出了泛型的概念,其實(shí)也就是類型參數(shù)的概念撬讽,此時(shí)可以用泛型創(chuàng)建List蕊连,List a = new ArrayListInteger,那么游昼,此時(shí)a.add(1)沒問題甘苍,而a.add("2")呢?就不行了烘豌,因?yàn)榉盒蜁?huì)限制载庭,只能往集合中添加Integer類型,這樣就避免了上述的問題廊佩。
那么Scala的類型參數(shù)是什么囚聚?其實(shí)意思與Java的泛型是一樣的,也是定義一種類型參數(shù),比如在集合鸯绿,在類瓶蝴,在函數(shù)中毒返,定義類型參數(shù),然后就可以保證使用到該類型參數(shù)的地方舷手,就肯定拧簸,也只能是這種類型。從而實(shí)現(xiàn)程序更好的健壯性男窟。
此外盆赤,類型參數(shù)是Spark源碼中非常常見的牺六,因此同樣必須掌握,才能看懂spark源碼汗捡。
泛型類
// 泛型類淑际,顧名思義,其實(shí)就是在類的聲明中艘蹋,定義一些泛型類型锄贼,然后在類內(nèi)部,比如field或者method簿训,就可以使用這些泛型類型咱娶。
// 使用泛型類,通常是需要對(duì)類中的某些成員强品,比如某些field和method中的參數(shù)或變量膘侮,進(jìn)行統(tǒng)一的類型限制,這樣可以保證程序更好的健壯性和穩(wěn)定性的榛。
// 如果不使用泛型進(jìn)行統(tǒng)一的類型限制琼了,那么在后期程序運(yùn)行過程中,難免會(huì)出現(xiàn)問題夫晌,比如傳入了不希望的類型雕薪,導(dǎo)致程序出問題。
// 在使用類的時(shí)候晓淀,比如創(chuàng)建類的對(duì)象所袁,將類型參數(shù)替換為實(shí)際的類型,即可凶掰。
// Scala自動(dòng)推斷泛型類型特性:直接給使用了泛型類型的field賦值時(shí)燥爷,Scala會(huì)自動(dòng)進(jìn)行類型推斷蜈亩。
案例:新生報(bào)到,每個(gè)學(xué)生來自不同的地方前翎,id可能是Int稚配,可能是String
class Student[T](val localId: T) {
def getSchoolId(hukouId: T) = "S-" + hukouId + "-" + localId
}
val leo = new Student[Int](111)
泛型函數(shù)
// 泛型函數(shù),與泛型類類似港华,可以給某個(gè)函數(shù)在聲明時(shí)指定泛型類型道川,然后在函數(shù)體內(nèi),多個(gè)變量或者返回值之間立宜,就可以使用泛型類型進(jìn)行聲明冒萄,從而對(duì)某個(gè)特殊的變量,或者多個(gè)變量赘理,進(jìn)行強(qiáng)制性的類型限制宦言。
// 與泛型類一樣,你可以通過給使用了泛型類型的變量傳遞值來讓Scala自動(dòng)推斷泛型的實(shí)際類型商模,也可以在調(diào)用函數(shù)時(shí)奠旺,手動(dòng)指定泛型類型。
案例:卡片售賣機(jī)施流,可以指定卡片的內(nèi)容响疚,內(nèi)容可以是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")
上邊界Bounds
// 在指定泛型類型的時(shí)候,有時(shí)瞪醋,我們需要對(duì)泛型類型的范圍進(jìn)行界定忿晕,而不是可以是任意的類型。比如银受,我們可能要求某個(gè)泛型類型践盼,它就必須是某個(gè)類的子類,這樣在程序中就可以放心地調(diào)用泛型類型繼承的父類的方法宾巍,程序才能正常的使用和運(yùn)行咕幻。此時(shí)就可以使用上下邊界Bounds的特性。
// Scala的上下邊界特性允許泛型類型必須是某個(gè)類的子類顶霞,或者必須是某個(gè)類的父類
案例:在派對(duì)上交朋友
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) {
def play = p1.makeFriends(p2)
}
下邊界Bounds
// 除了指定泛型類型的上邊界肄程,還可以指定下邊界,即指定泛型類型必須是某個(gè)類的父類
案例:領(lǐng)身份證
class Father(val name: String)
class Child(name: String) extends Father(name)
def getIDCard[R >: Child](person: R) {
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.")
}
View Bounds
// 上下邊界Bounds选浑,雖然可以讓一種泛型類型蓝厌,支持有父子關(guān)系的多種類型。但是古徒,在某個(gè)類與上下邊界Bounds指定的父子類型范圍內(nèi)的類都沒有任何關(guān)系拓提,則默認(rèn)是肯定不能接受的。
// 然而隧膘,View Bounds作為一種上下邊界Bounds的加強(qiáng)版代态,支持可以對(duì)類型進(jìn)行隱式轉(zhuǎn)換狐粱,將指定的類型進(jìn)行隱式轉(zhuǎn)換后,再判斷是否在邊界指定的類型范圍內(nèi)
案例:跟小狗交朋友
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(dog: Object): Person = if(dog.isInstanceOf[Dog]) {val _dog = dog.asInstanceOf[Dog]; new Person(_dog.name) } else Nil
class Party[T <% Person](p1: T, p2: T)
Context Bounds
// Context Bounds是一種特殊的Bounds胆数,它會(huì)根據(jù)泛型類型的聲明,比如“T: 類型”要求必須存在一個(gè)類型為“類型[T]”的隱式值互墓。其實(shí)個(gè)人認(rèn)為必尼,Context Bounds之所以叫Context,是因?yàn)樗诘氖且环N全局的上下文篡撵,需要使用到上下文中的隱式值以及注入判莉。
案例:使用Scala內(nèi)置的比較器比較大小
class Calculator[T: Ordering] (val number1: T, val number2: T) {
def max(implicit order: Ordering[T]) = if(order.compare(number1, number2) > 0) number1 else number2
}
Manifest Context Bounds
// 在Scala中,如果要實(shí)例化一個(gè)泛型數(shù)組育谬,就必須使用Manifest Context Bounds券盅。也就是說,如果數(shù)組元素類型為T的話膛檀,需要為類或者函數(shù)定義[T: Manifest]泛型類型锰镀,這樣才能實(shí)例化Array[T]這種泛型數(shù)組。
案例:打包飯菜(一種食品打成一包)
class Meat(val name: String)
class Vegetable(val name: String)
def packageFood[T: Manifest] (food: T*) = {
val foodPackage = new Array[T](food.length)
for(i <- 0 until food.length) foodPackage(i) = food(i)
foodPackage
}
協(xié)變和逆變
Scala的協(xié)變和逆變是非常有特色的咖刃!完全解決了Java中的泛型的一大缺憾泳炉!
舉例來說,Java中嚎杨,如果有Professional是Master的子類花鹅,那么Card[Professionnal]是不是Card[Master]的子類?答案是:不是枫浙。因此對(duì)于開發(fā)程序造成了很多的麻煩刨肃。
而Scala中,只要靈活使用協(xié)變和逆變箩帚,就可以解決Java泛型的問題真友。
案例:進(jìn)入會(huì)場
class Master
class Professional extends Master
// 大師以及大師級(jí)別以下的名片都可以進(jìn)入會(huì)場
class Card[+T] (val name: String)
def enterMeet(card: Card[Master]) {
println("welcome to have this meeting!")
}
// 只要專家級(jí)別的名片就可以進(jìn)入會(huì)場,如果大師級(jí)別的過來了膏潮,當(dāng)然可以了锻狗!
class Card[-T] (val name: String)
def enterMeet(card: Card[Professional]) {
println("welcome to have this meeting!")
}
Existential Type
// 在Scala里,有一種特殊的類型參數(shù)焕参,就是Existential Type轻纪,存在性類型。這種類型務(wù)必掌握是什么意思叠纷,因?yàn)樵趕park源碼實(shí)在是太常見了刻帚!
Array[T] forSome { type T }
Array[_]
隱式轉(zhuǎn)換與隱式參數(shù)
引言
Scala提供的隱式轉(zhuǎn)換和隱式參數(shù)功能,是非常有特色的功能涩嚣。是Java等編程語言所沒有的功能崇众。它可以允許你手動(dòng)指定掂僵,將某種類型的對(duì)象轉(zhuǎn)換成其他類型的對(duì)象。通過這些功能顷歌,可以實(shí)現(xiàn)非常強(qiáng)大锰蓬,而且特殊的功能。
Scala的隱式轉(zhuǎn)換眯漩,其實(shí)最核心的就是定義隱式轉(zhuǎn)換函數(shù)芹扭,即implicit conversion function。定義的隱式轉(zhuǎn)換函數(shù)赦抖,只要在編寫的程序內(nèi)引入舱卡,就會(huì)被Scala自動(dòng)使用。Scala會(huì)根據(jù)隱式轉(zhuǎn)換函數(shù)的簽名队萤,在程序中使用到隱式轉(zhuǎn)換函數(shù)接收的參數(shù)類型定義的對(duì)象時(shí)轮锥,會(huì)自動(dòng)將其傳入隱式轉(zhuǎn)換函數(shù),轉(zhuǎn)換為另外一種類型的對(duì)象并返回要尔。這就是“隱式轉(zhuǎn)換”舍杜。
隱式轉(zhuǎn)換函數(shù)叫什么名字是無所謂的,因?yàn)橥ǔ2粫?huì)由用戶手動(dòng)調(diào)用赵辕,而是由Scala進(jìn)行調(diào)用蝴簇。但是如果要使用隱式轉(zhuǎn)換,則需要對(duì)隱式轉(zhuǎn)換函數(shù)進(jìn)行導(dǎo)入匆帚。因此通常建議將隱式轉(zhuǎn)換函數(shù)的名稱命名為“one2one”的形式熬词。
Spark源碼中有大量的隱式轉(zhuǎn)換和隱式參數(shù),因此必須精通這種語法吸重。
隱式轉(zhuǎn)換
要實(shí)現(xiàn)隱式轉(zhuǎn)換互拾,只要程序可見的范圍內(nèi)定義隱式轉(zhuǎn)換函數(shù)即可。Scala會(huì)自動(dòng)使用隱式轉(zhuǎn)換函數(shù)嚎幸。隱式轉(zhuǎn)換函數(shù)與普通函數(shù)唯一的語法區(qū)別就是颜矿,要以implicit開頭,而且最好要定義函數(shù)返回類型嫉晶。
// 案例:特殊售票窗口(只接受特殊人群骑疆,比如學(xué)生、老人等)
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 Nil
}
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
使用隱式轉(zhuǎn)換加強(qiáng)現(xiàn)有類型
隱式轉(zhuǎn)換非常強(qiáng)大的一個(gè)功能替废,就是可以在不知不覺中加強(qiáng)現(xiàn)有類型的功能箍铭。也就是說,可以為某個(gè)類定義一個(gè)加強(qiáng)版的類椎镣,并定義互相之間的隱式轉(zhuǎn)換诈火,從而讓源類在使用加強(qiáng)版的方法時(shí),由Scala自動(dòng)進(jìn)行隱式轉(zhuǎn)換為加強(qiáng)類状答,然后再調(diào)用該方法冷守。
// 案例:超人變身
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
隱式轉(zhuǎn)換函數(shù)作用域與導(dǎo)入
Scala默認(rèn)會(huì)使用兩種隱式轉(zhuǎn)換刀崖,一種是源類型,或者目標(biāo)類型的伴生對(duì)象內(nèi)的隱式轉(zhuǎn)換函數(shù)拍摇;一種是當(dāng)前程序作用域內(nèi)的可以用唯一標(biāo)識(shí)符表示的隱式轉(zhuǎn)換函數(shù)亮钦。
如果隱式轉(zhuǎn)換函數(shù)不在上述兩種情況下的話,那么就必須手動(dòng)使用import語法引入某個(gè)包下的隱式轉(zhuǎn)換函數(shù)充活,比如import test._或悲。通常建議,僅僅在需要進(jìn)行隱式轉(zhuǎn)換的地方堪唐,比如某個(gè)函數(shù)或者方法內(nèi),用import導(dǎo)入隱式轉(zhuǎn)換函數(shù)翎蹈,這樣可以縮小隱式轉(zhuǎn)換函數(shù)的作用域淮菠,避免不需要的隱式轉(zhuǎn)換。
隱式轉(zhuǎn)換的發(fā)生時(shí)機(jī)
// 1荤堪、調(diào)用某個(gè)函數(shù)合陵,但是給函數(shù)傳入的參數(shù)的類型,與函數(shù)定義的接收參數(shù)類型不匹配(案例:特殊售票窗口)
// 2澄阳、使用某個(gè)類型的對(duì)象拥知,調(diào)用某個(gè)方法,而這個(gè)方法并不存在于該類型時(shí)(案例:超人變身)
// 3碎赢、使用某個(gè)類型的對(duì)象低剔,調(diào)用某個(gè)方法,雖然該類型有這個(gè)方法肮塞,但是給方法傳入的參數(shù)類型襟齿,與方法定義的接收參數(shù)的類型不匹配(案例:特殊售票窗口加強(qiáng)版)
// 案例:特殊售票窗口加強(qiáng)版
class TicketHouse {
var ticketNumber = 0
def buySpecialTicket(p: SpecialPerson) = {
ticketNumber += 1
"T-" + ticketNumber
}
}
隱式參數(shù)
// 所謂的隱式參數(shù),指的是在函數(shù)或者方法中枕赵,定義一個(gè)用implicit修飾的參數(shù)猜欺,此時(shí)Scala會(huì)嘗試找到一個(gè)指定類型的,用implicit修飾的對(duì)象拷窜,即隱式值开皿,并注入?yún)?shù)。
// Scala會(huì)在兩個(gè)范圍內(nèi)查找:一種是當(dāng)前作用域內(nèi)可見的val或var定義的隱式變量篮昧;一種是隱式參數(shù)類型的伴生對(duì)象內(nèi)的隱式值
// 案例:考試簽到
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.")
}