帶你走進Spark程式設計之Scala入門
寫在前邊的話:
1:什麼是Scala?
Scala是一門多正規化的程式語言,類似於Java,並集成了面向物件程式設計和函數語言程式設計的各種特性,具體可參考知乎上的一個帖子
2:本篇部落格包含哪些內容?
Scala中變數的宣告與函式定義
Scala中的控制結構
Scala中的資料型別
Scala中的類詳解
1:變數宣告與函式定義
變數宣告:val 和 var ,兩者的區別是val宣告的變數是不可變的,而var宣告的變數可變
eg:
scala> val a = 12; a: Int = 12 scala> a = 13 <console>:12: error: reassignment to val a = 13 ^ scala> var b =11 b: Int = 11 scala> b = 12 b: Int = 12
函式定義:
帶返回值:
scala> def max(x:Int,y:Int):Int = {
| if(x>y) x
| else y
| }
max: (x: Int, y: Int)Int
scala> max(1,2)
res5: Int = 2
不帶返回值:scala> def helloworld()=println("HelloWorld")
helloworld: ()Unit
2:控制結構
1) 判斷(if)
類似於上邊函式定義中的max函式
2) 迴圈(while/do)
while 語句包括狀態判斷和迴圈體,只要當前狀態判斷為真,就執行迴圈體一遍,然後進行下一狀態判斷,判斷為假時終止判斷,形如 while (A)B
do語句和while相反,先執行迴圈體一遍,然後進行狀態判斷,狀態判斷為真,則繼續執行迴圈體,否則終止迴圈,形如 do B while(A)
scala> var m =3 m: Int = 3 scala> while(m!=0){ | println(m) | m -= 1 | } 3 2 1 scala> var n =3 n: Int = 3 scala> do{ | println(n) | n -= 1 | }while(n!=0) 3 2 1
3) 列舉(for)
for的兩種實現
scala> for(i<- 1 to 3)
| println(i)
1
2
3
scala> for(i<- 1 until 3)
| println(i)
1
2
4) 匹配(match表示式)
scala中的match類似於其他語言中的switch,從上往下進行匹配
scala> val a = "gyt"
a: String = gyt
scala> a match{
| case "gyt" => println("OK")
| case "cyan" => println("no")
| }
OK
5) 異常處理(throw/try)
Scala通過throw丟擲一個異常,其異常捕獲和處理與java十分類似
scala> if(true){
| println("throw new exception")
| throw new IllegalArgumentException
| }
throw new exception
java.lang.IllegalArgumentException
... 35 elided
scala> try{
| val file = new FileReader("input.txt")
| }catch{
| case ex: FileNotFoundException => //handle missing file
| case ex: IOException => //handle other I/O error
| }finally{
| println("end")
| }
end
6) 輸出(print/println)
一個不換行輸出,一個換行輸出
7) 輸入(readline)
輸入通常使用read函式,readline是從控制檯輸如一行,指定型別為readT,T為型別,例如readInt
scala> val name = readLine("Your Name: \n")
warning: there was one deprecation warning; re-run with -deprecation for details
Your Name:
name: String = thinkgamer
8) 其他語句
return語言表示返回某個值,但是Scala事實上無需使用return語句,對於函式來說,其預設返回值是最後出現的一個值,不用特別註明,如需要返回的值,並非最後出現時,可在函式體後加上該值的識別符號使之出現,宣告函式時使用return語句,必須宣告返回值型別,例如 def max:T = {return}
break/continue在C++中非常常見的控制結構語句,但在Scala中是不必要的,可以使用布林值型別的兩通過if語句進行控制
3:資料結構
1) 陣列
使用new來例項化一個類,當你建立一個物件的例項時,你可以使用數值或者型別引數
scala> val abc = new Array[String](3)
abc: Array[String] = Array(null, null, null)
scala> abc(0)="thinkgamer"
scala> abc(1)="cyan"
scala> abc(2)="GQ"
scala> for(i<- 0 to 2)
| println(abc(i))
thinkgamer
cyan
GQ
這裡也說明下為什麼Scala使用()來訪問陣列元素,在Scala中,陣列和其他普遍的類的定義一樣,沒有什麼特別之處,當你某個值後面使用()時,Scala將其翻譯成對應物件的apply方法,因此本例中abc(0)其實呼叫abc.apply(0)方法,這種表達方法不僅僅只限於資料,對於任何物件,如果在其後面使用(),都將呼叫該物件的apply方法,同一,日過對某個使用()的物件複製,比如
abc(0)="thinkgamer"
scala將這種複製轉換為該物件的update方法,也就是abc.update(0,"thinkgamer"),因此上邊的例子也可以使用傳統的方法呼叫,可以寫成:
scala> val new_abc = new Array[String](3)
new_abc: Array[String] = Array(null, null, null)
scala> new_abc.update(0,"aaa")
scala> new_abc.update(1,"bbb")
scala> new_abc.update(2,"ccc")
scala> for(i<- 0 to 2)
| println(new_abc(i))
aaa
bbb
ccc
從這點來收,陣列在scala中並不是某種特殊的資料型別,和普通的類並沒有什麼區別
不過scala還是提供了初始化陣列的簡單的方法,上述的例子可以這樣寫:
scala> val abc = Array("thinkgamer","cyan","GQ")
abc: Array[String] = Array(thinkgamer, cyan, GQ)
陣列的輸出可以採用while或者foreach或者for進行輸出scala> val abc = Array("thinkgamer","cyan","GQ")
abc: Array[String] = Array(thinkgamer, cyan, GQ)
scala> var i = 0
i: Int = 0
scala> while(i<abc.length){
| println(abc(i))
| i+=1
| }
thinkgamer
cyan
GQ
scala> abc.foreach(str=>println(str))
thinkgamer
cyan
GQ
2):Lists
Scala的List和Java不同,不能被改變,這樣做的一個好處是方法與方法之間關聯性較小,從而方法變得更可靠和重用性高,使用這個規則也就意味著變數的設定是不可修改的,這也就避免了多執行緒訪問的互鎖問題scala> val one = List(1,2,3)
one: List[Int] = List(1, 2, 3)
scala> val two = List(4,5)
two: List[Int] = List(4, 5)
scala> val three = one:::two
three: List[Int] = List(1, 2, 3, 4, 5)
:::方法表示連線兩個列表,當然列表定義了::方法(右操作符),用於向列表新增元素
scala> val four = three::6::7::Nil
four: List[Any] = List(List(1, 2, 3, 4, 5), 6, 7)
scala> val five = 1::2::3::4::Nil
five: List[Int] = List(1, 2, 3, 4)
Nil表示空列表
Scala的List類還定義了其他很多很有用的方法,比如head,last,length,reverse,tail等這裡就不一一說明了,具體可以參考List的文件
3) Tuples
Scala中另外一個很有用的容器類是Tupels,和Lists不同的Tuples可以包含不同型別的資料,而List只能包含同類型的資料,Tuples在方法需要返回多個結果時非常有用(Tuple對應數學的向量的概念)一旦定義了一個元組,可以使用._和索引來訪問元組的元素(向量的分量,注意和陣列不同的是,元組的索引從1開始)
scala> val pair = (22,"one")
pair: (Int, String) = (22,one)
scala> println(pair._1)
22
scala> println(pair._2)
one
元組的實際型別取決於它的分量的型別,比如上邊的pair的型別實際為Tuple2[Int,String],目前Scala支援的元組的最大長度為22,如果有需要,你可以擴充套件更長的元組
4) Set
scala> var set = Set("a","b")
set: scala.collection.immutable.Set[String] = Set(a, b)
scala> set+="c"
scala> set
res8: scala.collection.immutable.Set[String] = Set(a, b, c)
scala> println(set.contains("c"))
true
預設情況Set為Immutable Set,如果你需要使用可修改的集合類(Set型別),你可以使用全路徑來指明Set,比如scala.collection.mutalbe.Set
5) Map
Map的基本用法如下(Map類似於其他語言中的關聯資料如PHP)
scala> val roman =Map(1->"I",2->"II")
roman: scala.collection.immutable.Map[Int,String] = Map(1 -> I, 2 -> II)
scala> println(roman(1))
I
scala> println(roman(2))
II
4:類與物件
1) 單例與伴生物件
scala比java更面向物件的一個方面是Scala沒有靜態成員,替代品是scala有單例物件(singleton object),當單例物件與某個類共享同一個名稱時,他被稱作是這個類的伴生物件(companion object),你必須在同一個原始檔裡定義類和他的伴生物件,類被陳我給是這個單例物件的伴生類(companion class),類和他的伴生物件可以互相訪問其私有成員,例如,一個原始檔大致如下:
object A{
//A 是一個單例物件
//Object A 與Class A同名,故 是class A 的伴生物件
}
class A{
// 類A與單例物件A同名
// 故class 是 object A 的伴生類
//兩者可以互相訪問私有成員
}
定義單例物件不是定義型別(在Scala的抽象層次上說)
類和單例物件的一個差別是,單例物件不帶引數,而類可以,因為你不能用new關鍵字例項化一個單例物件,你沒機會傳遞給他引數,每個單例物件都被作為由一個靜態變數指向的虛構類,synthetic class的一個例項來實現,因此他們與java靜態類有著相同的初始化語法。Scala程式特別要指出的是:單例物件會在第一次被訪問的時候初始化
不與伴生類共享名稱的單例物件成為孤立物件:standlone object。最常見的就是程式入口:
scala> object ObjecOps {
| def main(args: Array[String]): Unit = {
| println(args.length)
| }
| }
defined object ObjecOps
scala> var arr = Array("1","2")
arr: Array[String] = Array(1, 2)
scala> ObjecOps.main(arr)
2
2) 主構造器和輔助構造器
主構造器:每個類都有主構造器,且與類的定義交織在一起,主構造器的引數緊跟在類名之後,如 class helloworld(val hello:String,val world:String){...},主構造器的引數在傳入時,就已經被編譯成欄位,並在構造物件時初始化傳入,一個類若沒有顯式定義主構造器,自動擁有一個無參主構造器,若類中有直接執行的語句(非定義的方法、函式等),每次構造物件時皆會執行一次,不論是什麼樣的構造器型別,如:
scala> class HelloWorld(val v1:String,val v2:String){
| println("Hello World")
| val v3 = v1+v2
| }
defined class HelloWorld
scala> val one = new HelloWorld("welcome","thinkgamer")
Hello World
one: HelloWorld = [email protected]
scala> val two = new HelloWorld("My Name is","thinkgamer")
Hello World
two: HelloWorld = [email protected]
輔助構造器:Scala類可以有任意多個構造器,輔助構造器的名稱為this,在類中定義,輔助盜走阿七必須以一個主構造器活其他一定義的輔助構造器呼叫開始,例如:scala> class GYT{
| private var v1 = ""
| private var v2 = ""
| def this(m:String) {
| this()
| this.v1=m
| }
| def this(m:String,n:String) {
| this(m)
| this.v2 = n
| }
| def get(){
| println(v1+"\t" + v2)
| }
| }
defined class GYT
scala> var one = new GYT("A")
one: GYT = [email protected]
scala> one.get()
A
scala> var one = new GYT("A","B")
one: GYT = [email protected]
scala> one.get()
A B
3) 巢狀類
scala允許任何語法結構中巢狀任何語法結構,因此能在類中定義類,例如:
scala> class A{
| class B{
| println("this B")
| }
| println("this A")
| }
defined class A
scala> val one = new A()
this A
one: A = [email protected]
對於同一個外部類,不同例項下的內部類是不同的,形如val one = new A() 與val two = new A(),one.B 和 two.B是兩個不同的類
內部類中可以呼叫外部類的策劃那個元,利用外部類.this或指標實現,例如:
scala> class Hello{
| pointto=>
| val value1 = ""
| class Hi{
| val value2 = Hello.this.value1
| val value3=pointto.value1
| }
| }
defined class Hello
scala> val one = new Hello
one: Hello = [email protected]
scala> val two = new one.Hi
two: one.Hi = [email protected]
scala> two.value2
res4: String = ""
scala> two.value3
res5: String = ""
java的內部類(巢狀類是從屬於外部類的,而scala的內部類是從屬於物件的),再看一個例項加深對scala內部類的理解:
scala> //定義外部類
scala> class Outer(val name:String){
| outer=>
| class Inner(val name:String){
| def foo(b:Inner)=println("Outer" + outer.name+" Inner" + b.name)
| }
| }
defined class Outer
scala> val out1 = new Outer("Spark")
out1: Outer = [email protected]
scala> val out2 = new Outer("Scala")
out2: Outer = [email protected]
scala> val in1 = new out1.Inner("hadoop")
in1: out1.Inner = [email protected]
scala> val in2 = new out2.Inner("Hbase")
in2: out2.Inner = [email protected]
scala> in1.foo(in1)
OuterSpark Innerhadoop
scala> in2.foo(in2)
OuterScala InnerHbase
4) apply方法
需要構造有引數需求的伴生物件時,可定義並使用apply方法,例如:
scala> class Hello(var m:String,n:Char){
| println(m + "\t" + n)
| }
defined class Hello
scala> object Hello{
| def apply(n:Char) = new Hello("|" ,n )
| }
defined object Hello
warning: previously defined class Hello is not a companion to object Hello.
Companions must be defined together; you may wish to use :paste mode for this.
scala> val hi = Hello('j')
| j
hi: Hello = [email protected]
在建立例項時若使用new來建立則使用的是class,若直接使用雷鳴建立物件則使用的是object建立物件5) 擴充套件
extends是Scala中實現繼承的保留字,形如 class week extends month{...},week類繼承了month類的所有非私有成員,在這裡week是month的子類,month是week的超類,子類能夠重寫超類的成員(具有相同的名稱和引數),另外單例物件同樣能從類中繼承,與類的繼承語法相同
6) 重寫
Scala中使用override保留字進行方法、欄位重寫,形如:class week extends month{ override def firstday = {...} }
override保留字實際使用類似於private,宣告這個保留字後的定義、宣告是對超類的重寫,因此,其也可以寫在類定義的引數中,形如:
class week(override val lastday:String) extends month{...}
重寫包括欄位和方法,單引數不同的方法可以不重寫,例如
class month {def secondday (m:String) = {...} }
class week extends month { def secondday = {...} }
進行重寫時有以下一些規則:
重寫def:用val,利用val能重寫超類用沒有引數的方法(getter)
用def,子類的方法與超類方法
用var,同時重寫getter、setter方法,只重寫getter方法報錯
重寫val:用val,子類的一個私有欄位與超類的欄位重名,getter方法重寫超類的getter方法
重寫var:用var,且當超類的var是抽象的才能被重寫,否則超類的var都會被繼承
看一個包含重寫,繼承和主構造器的例子:
scala> class Month{
| var day = 0
| private var month = 0
| def this(now_day:Int,now_month:Int){
| this()
| this.day = now_day
| this.month = now_month
| }
| def add(){
| day+=1 % 30
| month += (day / 30)
| println("day is:" + day + "\nmonth is:" + month)
| }
| }
defined class Month
scala> class Week extends Month{
| private var week = 0
| def this(now_day:Int,now_week:Int){
| this()
| this.day = now_day
| this.week = now_week
| }
| override def add():Unit={
| day+=1% 7
| week += (day/7)
| println("day is:" + day + "\nweek is:" + week)
| }
| }
defined class Week
scala> var m1 = new Month(1,1)
m1: Month = [email protected]
scala> m1.add
day is:2
month is:1
scala> m1.add
day is:3
month is:1
scala> var w1 = new Week(1,1)
w1: Week = [email protected]
scala> w1.add
day is:2
week is:1
scala> w1.add
day is:3
week is:1
scala> m1.add
day is:4
month is:1
7) 抽象
不能被例項化的類叫做抽象類,抽象類的某個或者某幾個成員沒有被完整定義,這些沒有被完整定義的成員稱為抽象方法或抽象欄位,用abstract保留字標記抽象類,例如:
scala> abstract class year{
| val name:Array[Srting] //抽象的val,帶有一個抽象的getter方法
| var num:Int //抽象的var,帶有抽象的getter/setter方法
| def sign //沒有方法體/函式體,是一個抽象方法
| }
只要類中有任意一個抽象成員,必須使用abstract標記
重寫抽象方法,抽象欄位不需要使用override保留字
scala> abstract class animal{
| var name :String
| var age : Int
| def show
| }
defined class animal
scala> class dog extends animal{
| override var name ="dog"
| override var age = 23
| override def show(){
| println("name:" + this.name + "\tage: " + this.age)
| }
| }
defined class dog
scala> var d = new dog
d: dog = [email protected]
scala> d.show
name:dog age: 23
//重寫抽象方法不需要使用override保留字
scala> class pig extends animal{
| var name ="pig"
| var age = 23
| def show(){
| println("name:" + this.name + "\tage: " + this.age)
| }
| }
defined class pig
scala> var p = new pig
p: pig = [email protected]
scala> p.show
name:pig age: 23
8) 保護
當一個類不希望被繼承、拓展時,可在類宣告前加上final保留字,形如 final class year{...}
當一個類的某些成員不希望被重寫時,可以在成員宣告前加上final保留字,形如 class year {final def sign{...} }
當超類中的某些成員需要被子類繼承,又不想對子類以外成員可見時,在成員宣告前加上protected保留字,protected[this],將訪問許可權定於當前物件,類似於private[this]
類中protected的成員對其子類可見,對其超類不可見,形如:class year{ protected def sign {...} }
“保護”的應用:
子類構造器的執行在超類構造器執行之後,在超類的構造器中呼叫的成員被子類重寫後,返回值可能不正確,如下邊的例子
scala> class month{
| val num = 31
| val days = new Array[Int](num)
| }
defined class month
scala> class week extends month{
| override val num = 7
| }
defined class week
scala> val a = new week
a: week = [email protected]
scala> a.days
res4: Array[Int] = Array()
我們可以看到 Array的長度變成了0,但是我們month類中明明定義為num長度,原因為何?
構造week物件前先執行超類month的構造器,num被初始化為31,month為初始化days陣列,呼叫num,但是num被子類重寫了,但因為week構造器還沒被呼叫,此時num的值未被初始化,因而為0,days被設定為長度為0的陣列,month構造器執行完畢,執行week構造器,num被初始化為7
解決辦法:將超類的val宣告為final,將超類的val宣告為lazy
scala> class month{
| final val num = 31
| val day = new Array[Int](num)
| }
defined class month
scala> class week extends month{
| }
defined class week
scala> val w = new week
w: week = [email protected]
scala> w.day
res10: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
scala> class month{
| lazy val num = 31
| val day = new Array[Int](num)
| }
defined class month
scala> class week extends month{
| }
defined class week
scala> val w = new week
w: week = [email protected]
scala> w.day
res11: Array[Int] = Array(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
另外一中辦法是在子類中使用提前定義語法,那麼什麼事提前定義?
提前定義是在超類的構造器執行之前初始化子類的欄位,把需要提前定義的語句塊放在extends與超類之間,並後接with保留字,形如
class week extends {override val num= 7} with month{...}
提前定義中=號右側若需呼叫類B中成員時,除非B成員已在呼叫前提前定義
scala> class week extends{
| override val num = 7
| override val num2 = num +1 //允許,num已被提前定義
| override val num4= num2+num3 //不允許,num3沒有在之前提前定義
| } with month{...}
9) 特質
Scala不支援多重繼承,取而代之的是特質,一個子類只能擁有一個超類,一個超類能擁有多個子類,即class week extends month,year是不合法的,若一個子類繼承自不同的超類,不同的超類中同名子類不知如何處理,多重繼承會產生菱形繼承問題,解決多重繼承可能導致的問題消耗的資源遠比多重繼承產生的價值高
特質定義
Scala裡程式碼複用的基礎單元,封裝了方法和欄位的定義,一個類可以擴充套件自一個或多個特質,一個特質可以被多個類擴充套件,而且特質能限制被什麼樣的類所擴充套件,特質的定義使用的保留字是trait,具體語法和類定義相似,除了不能擁有構造引數,比如說我們來定義一個特質:
scala> trait reset{
| def reset(m:Int,n:Int)=if(m>n) 1
| }
defined trait reset
一旦特質被定義了,就可以混入類中
scala> class week extends reset{
| println("add class")
| }
defined class week
當要混入多個特質時,利用with保留字
scala> trait A{
| }
defined trait A
scala> trait B{}
defined trait B
scala> trait C{}
defined trait C
scala> class week extends A with B with C {}
defined class week
特質的性質
成員時可以抽象的,而且不需要使用abstract宣告,同樣的,重寫特質的抽象方法無需給出override,但是多個特質重寫同一個特質的抽象方法需給出override,除了在類定義中可以混入特質,在特質定義中也可以混入特質
scala> abstract trait A{
| var a:Int
| def show
| }
defined trait A
scala> trait B extends A{
| override var a:Int
| def show(){
| println()
| }
| }
defined trait B
在物件構造時也可以混入特質,形如 val one = new month with resering
特質的構造也是有順序的,從左到右被構造,構造器按超類->父特質->第一個特質->第二個特質(父特質不重複構造)->類 的順序構造
如果 class A extens B1 with B2 with B3 ...那麼串聯B1,B2,B3...等特質,去掉重複項且右側勝出
特質的應用
(1):介面,根絕已有的方法為類新增方法
例項,利用特質實現富介面,即構造一個具有少量方法和大量抽象方法的具體方法的特質,那麼只要把特質混入類中,通過類重寫抽象方法後,類便可以自動獲取大量的具體方法
scala> trait Logger{
| def log(msg:String)
| def warn(msg:String){}
| def server(msg:String) {}
| }
defined trait Logger
scala> class week extends Logger{
| def log(msg:String){
| println(msg)
| }
| server("test")
| }
defined class week
(2) 為類提供可堆疊的改變(super保留字)
當為類新增多個互相呼叫的特質時,從最後一個開始進行處理,在類中super.foo()這樣的方法呼叫時靜態繫結的,明確是呼叫它的父類的foo()方法,在特質中寫下了super.foo()時,它的呼叫是動態繫結的,呼叫的實現在每一次特質被混入到具體類的時候才被繫結,因此特質混入的次序的不同其執行效果也就不同
scala> abstract class IntQueue{
| def get():Int
| def put(x:Int)
| }
defined class IntQueue
scala> class BasicIntQueue extends IntQueue{
| private val buf = new scala.collection.mutable.ArrayBuffer[Int]
| def get()=buf.remove(0)
| def put(x:Int){
| buf += x
| }
| }
defined class BasicIntQueue
scala> trait Incrementing extends IntQueue{
| abstract override def put(x:Int){
| super.put(x+1)
| }
| }
defined trait Incrementing
scala> trait Doubling extends IntQueue{
| abstract override def put(x:Int){
| super.put(2*x)
| }
| }
defined trait Doubling
scala> object TestClient extends App{
| val queue = (new BasicIntQueue with Incrementing with Doubling)
| queue.put(2)
| println(queue.get())
|
| val queue2 = (new BasicIntQueue with Doubling with Incrementing)
| queue2.put(2)
| println(queue2.get())
| }
defined object TestClient
PS:終於寫完了,好累.....