1. 程式人生 > >Spark(七) -- Scala快速入門

Spark(七) -- Scala快速入門

Scala作為Spark的開發語言,想要成為Spark高手,精通Scala是必須要走的一條路
然後一門語言並不是你想精通就能夠精通的,更何況是Scala這種面向物件又面向函式的程式語言,個人覺得其學習的門檻會比C#,Java等面嚮物件語言要高
所以,這篇文章是建立在有一點程式語言知識的基礎上的(如學過C#或者Java等),其實所有語言都是大同小異的,學會了一門語言在學其他的就不會像剛開始那麼吃力了,因為它們很多概念都是相通的
本篇文章主要是介紹Scala本身的一些特性,以便以能夠快速的上手開發,而對於真正要精通Scala,顯然要付出的努力還要很多

安裝Scala的開發環境就不具體介紹了,請自行百度之。IDE使用的是Eclipse For Scala,下載地址:

Eclipse For Scala

本文大概從以下幾個方面來介紹Scala:

  1. 宣告變數的關鍵字
  2. 方法的定義格式
  3. 條件表示式
  4. 預設引數,帶名引數和變長引數
  5. lazy變數
  6. 陣列
  7. Map操作
  8. 元組
  9. 值函式
  10. 匿名函式
  11. 函式柯里化
  12. 高階函式示例
  13. List序列
  14. case class和模式匹配

Scala中宣告變數的關鍵字只有兩種:
val和var,分別表示常量和變數的宣告(在Scala提倡中儘量使用val而不是var)

方法的定義格式:
def 方法名(引數名:引數型別,…):返回值型別={方法體}
如:

def add(x:Int,y:Int):Int={
    x + y
}

定義了一個引數為兩個Int型別,返回值也為Int型別的方法add,返回x+y的值
相信已經有人注意到了,在Scala中,每個句子是不用以分號結束的(當然加上分號也沒事),而且方法中不用return來返回,預設返回的是最後一行的值
當方法沒有返回值時表現形式為:

def add():Unit={...}

或者直接省略:Unit

def add()={...}

在呼叫方法的時候,如果該方法沒有引數,可以省略括號,如:

add//加上括號也沒事,如add()

條件表示式:

和其他語言的if/else唯一的卻別就是,Scala的if/else是有返回值的

val x = 8
val res = if(x > 7) 1 else 0

如果x>7就返回1,否則返回0,賦值給res

Scala中的while和do-while用法是和其他語言一樣的,但是for的用法就不同了
for迴圈語句的格式為:

for(i <- 表示式){
    迴圈體
}

例如:

for(i <- 1 to 10){
    println(i)
}

上面程式碼中,1 to 10會產生1-10中的每個數,i會迭代迴圈這10個數,迭代一次執行一次迴圈體。
剛開始看肯定會很奇怪,<-這個符號是必須的,現在假設我們有一個arr陣列,裡面有1-10這10個數
首先用C#的迴圈方式為:

for(int i = 0;i < arr.Length;i++)
{
    Console.WriteLine(arr[i]);
}
//或者foreach方式
foreach(var i in arr)
{
    Console.WriteLine(i);
}

在對比一下Scala的for迴圈:

for(i <- 0 to arr.length){
    println(arr[i])
}
//for同時也可以當foreach使用
for(i <- arr){
    println(i)
}

並且,在Scala中,for迴圈是可以新增if判斷語句的,如:

for(i <- 0 to 10 if i % 2 == 0){
    println(i)
}

上面程式碼的意思是,迴圈0-10,只打印出偶數
Scala的迴圈表示式中沒有continue和break語句,但是可以通過一個Boolean變數和巢狀函式中return來實現

預設引數,帶名引數和變長引數:

這幾個概念很簡單,通過幾行例項程式碼基本可以瞭解
預設引數:

def getName(name:String = "JChubby") = {...}

呼叫getName時如果沒有傳引數,則會使用預設的JChubby

帶名引數:

def showNum(x:Int,y:Int):Unit = {...}

呼叫該方法時,引數可以不按順序,但是要執行引數名:

showNum(y = 2,x = 4)

變長引數:

def canChangeLength(x:Int*) = {...}

呼叫時:

canChangeLength(1,2,3,4,...)

lazy變數:

正常情況下使用val或者var宣告變數時是直接分配記憶體空間使用的
當使用lazy關鍵字時,這個常/變數只有在使用的時候才會被分配記憶體
如在Scala命令列中輸入下面的程式碼之後回車:

lazy val a = 1

這裡寫圖片描述

lazy(懶值)對應開銷很大的初始化操作非常有用,如在讀取一個很大的檔案的時候,如果一開始並不馬上使用,可以將其標記為lazy,直到檔案真正使用的時候才分配記憶體空間

陣列:

Scala中分為兩種:
定長陣列:scala.collection.immutable.Array,一旦宣告之後長度不可變
變長陣列:scala.collection.mutable.ArrayBuffer,動態陣列

定長陣列使用:

val arr = new Array[Int](3)//宣告長度為3,型別為Int的Array陣列(new可省略)

//或者宣告時直接賦值
//編譯器會自動推斷型別為Int
val arr = Array(1,2,3)

//通過下標訪問陣列的方式為()而不是[],請注意!
val a = arr(0)

變長陣列使用:

val arrBuf = scala.collection.mutable.ArrayBuffer[Int]()
//新增元素,相當於add
arrBuf += 1
//也可以一次性新增多個
arrBuf += (1,2,3)
//使用++=可以直接新增一個數組
arrBuf ++= Array(1,2,3)
//在指定位置插入若干個元素,在0位置插入1,2,3
arrBuf.insert(0,1,2,3)
//Scala中的函式除了通過.來呼叫之外也可以直接寫成一下形式
arrBuf insert (0,1,2,3)
//之前的1 to 10 其實就是呼叫了1.to(10)這個to方法

//移除指定位置的元素
arrBuf remove 0
//移除指定位置後的連續n個元素
arrBuf remove 0,3
//刪除末尾的n個元素
arrBuf trimEnd 3

此外還有例如min,max等函式求最大最小等常用操作

Map操作:

和陣列一樣,Map同樣也分為可變和不可變兩種

不可變Map的使用:

//宣告一個Map,包含兩個鍵值對,->表示鍵值對應的關係
val myMap = Map("JChubby" -> 22,"Looky" -> 21)

不可變的Map中,一旦宣告之後,該Map不可新增不可刪除不可修改,只能檢視,檢視方式與可變Map一致

可變Map的使用:

val myMap = scala.collection.mutable.Map("JChubby" -> 22,"Looky" -> 21)
//新增元素
myMap += ("abc" -> 12,"bcd" -> 13)
//刪除元素
myMap -= "abc"
//修改元素
myMap("JChubby") = 10
//或者
myMap += ("JChubby" -> 10)
//查詢元素
myMap("JChubby")
//或者使用getOrElse,該方法在取不到值的時候返回一個預設值,以免出現異常
myMap getOrElse ("JChubby",-1)
//遍歷,使用的是k,v格式來進行遍歷
for((k,v) <- myMap){
    println(k + ":" + v)
}
//如果只要遍歷k或者v,可以使用_佔位符
for((k,_) <- myMap){
    println(k)
}
//獲取所有的key
myMap.keySet
//獲取所有的value
myMap.values

元組:

元組其實就是一個集合,但是可以存放各種不同型別的資料
例如:

val group = (1,2,"JChubby",3.0)
//通過下標訪問元組的格式為:元組名._下標 或者 元組名 _下標。如:
group._0
group _0
//遍歷元組格式為,productIterator為固定格式不可少
for(element <- group.productIterator){
    ...
}

值函式:

Scala中可以將函式賦值給一個變數,這個變數就稱為值函式

def add(x:Int,y:Int):Int = {
    x + y
}

//格式為方法名+空格+_
var res = add _
//之後可以將這個變數當做方法來用
res(1,2)

匿名函式:

顧名思義,沒有名字的函式,定義格式如下:
(引數名:引數型別,…) => 表示式
例如:

(x:Int) => x + 3

可以將匿名函式賦值給一個常量

val func = (x:Int) => x + 3
//直接呼叫常量
func(7)

上面的用法其實和直接定義一個有名的函式是一樣的,匿名函式主要是當做函式的引數來傳遞使用
例如scala.collection.mutable.ArrayBuffer的map方法,其引數要求是一個匿名函式

scala.collection.mutable.ArrayBuffer.map((x:Int) => x +3)

執行的結果是ArrayBuffer裡面每個值都加3(map函式之後會介紹)

函式柯里化:

函式柯里化就是將原本有 很多引數 的函式,分成一個個只有 一個引數 的函式,每個函式的返回值都是 一個表示式 並且當做引數 傳到下一個函式
例如:

def mul(x:Int,y:Int) = x + y
//呼叫方式為
mul(1,2)
//柯里化後的函式為
def mul(x:Int)(y:Int) = x + y
//呼叫當時為
mul(1)(2)
//執行mul(1)時,返回的是1 + y,在將這個函式應用到2,得到 1 + 2

柯里化是閉包的典型體現,具體柯里化的概念請百度之~

高階函式示例:

陣列的map函式

val arr = Array(1,2,3)
arr.map(1 + _)

_表示陣列中的每個元素,執行的結果為每個元素+1

陣列的filter函式

arr.filter(_ > 2)

_ > 2會將陣列中大於2的元素過濾出來

陣列的reduce函式

arr.reduce(_ + _)

reduce是迭代運算,將陣列中兩個元素先執行操作,把結果作為一個元素繼續和下一個元素進行操作,執行結果為:1+2=3,3+3=9,也就是求和

List序列:

List操作:

val list = List(1,2,3)
//獲取頭元素
list.head
//獲取除了頭元素之外的其他元素
list.tail
//新增元素
7::list
//注意,::運算子是從右向左的

//獲取前n個元素
list.take(n)
//list的zip操作
val list1 = List(a,b,c)
list.zip(list1)
//得到的結果為List((1,a),(2,b),(3,c)),如果兩個list的長度不同,以比較短的為基礎

//flatten操作
val list2 = List(List(1,2),List(2,3),List(3,4))
list2.flatten
//得到的結果為List(1,2,2,3,3,4),用於將巢狀List組合成一個新的List

//flatmap操作
list2.flatmap(_.map(_ * 2))
//跟flatten相比,flatmap在組合之前,會先對每個子List進行map操作

case class和模式匹配:

case class又稱為樣例類,和普通的類相比,樣例類不用new就可以例項化,預設執行的是apply方法來構造物件

模式匹配:

和其他語言的switch功能差不多,但是Scala裡面使用的是match
使用示例:

val res = 1
res match{
    case 0 => println(0)
    case 1 => println(1)
    case 2 => println(2)
}

除了用法格式上略有不同,其餘的基本類似,並且不需要使用break關鍵字

樣例類和模式匹配結合使用:

//定義抽象類Human
abstract class Human
//分別定義用例類Chinese,Japanese,American都繼承自Human
case class Chinese(name:String) extends Human
case class Japanese(name:String) extends Human
case class American(name:String) extends Human

//定義模式匹配方法
def caseMatch(human:Human){
    //human引數型別不同,做出不同處理,Chinese(_)表示只關心是不是Chinese而不關心叫什麼名字
    case Chinese(_) => println("Chinese")
    case Japanese(_) => println("Japanese")
    case American(_) => println("American")
}