1. 程式人生 > >Scala程式設計快速入門系列(一)

Scala程式設計快速入門系列(一)

目    錄

一、Scala概述

二、Scala資料型別

三、Scala函式

四、Scala集合

五、Scala伴生物件

六、Scala trait

七、Actor

八、隱式轉換與隱式引數

九、Scala JDBC

由於整理的篇幅較長,所以文章計劃分三次釋出。

一、Scala概述

1. Scala簡介

  Scala是一種針對JVM將函式和麵向物件技術組合在一起的程式語言。所以Scala必須要有JVM才能執行,和Python一樣,Scala也是可以面向物件和麵向函式的。Scala程式語言近來抓住了很多開發者的眼球。它看起來像是一種純粹的面向物件程式語言,而又無縫地結合了命令式和函式式的程式設計風格。Scala的名稱表明,它還是一種高度可伸縮的語言。Scala的設計始終貫穿著一個理念:創造一種更好地支援元件的語言。Scala融匯了許多前所未有的特性,而同時又運行於JVM之上。隨著開發者對Scala的興趣日增,以及越來越多的工具支援,無疑Scala語言將成為你手上一件必不可少的工具。Spark最最源生支援的語言是Scala。Spark主要支援java、Scala、Python和R。Scala的底層協議是akka(非同步訊息傳遞)。

2. Scala安裝與開發工具

  JDK使用jdk-1.8。

二、Scala資料型別

1. 資料型別

 

  scala擁有和java一樣的資料型別,和java的資料型別的記憶體佈局完全一致,精度也完全一致。其中比較特殊的型別有Unit,表示沒有返回值;Nothing表示沒有值,是所有型別的子型別,建立一個類就一定有一個子類是Nothing;Any是所有型別的超類;AnyRef是所有引用型別的超類;注意最大的類是Object。

  上表中列出的資料型別都是物件,也就是說scala沒有java中的原生型別。在scala是可以對數字等基礎型別呼叫方法的。例如數字1可以調方法,使用1.方法名。

  如上兩圖所示,可見所有型別的基類與Any。Any之後分為兩個AnyVal與AnyRef。其中AnyVal是所有數值型別的父型別,AnyRef是所有引用型別的父型別。

  與其他語言稍微有點不同的是,Scala還定義了底型別。其中Null型別是所有引用型別的底型別,及所有AnyRef的型別的空值都是Null;而Nothing是所有型別的底型別,對應Any型別;Null與Nothing都表示空。

  在基礎型別中只有String是繼承自AnyRef的,與Java,Scala中的String也是記憶體不可變物件,這就意味著,所有的字串操作都會產生新的字串。其他的基礎型別如Int等都是Scala包裝的型別,例如Int型別對應的是Scala.Int只是Scala包會被每個原始檔自動引用。

  標準類庫中的Option型別用樣例類來表示拿著可能存在、也可能不存在的值。樣例子類Some包裝了某個值,例如:Some(“Fred”);而樣例物件None表示沒有值;這比使用空字串的意圖更加清晰,比使用null來表示缺少某值的做法更加安全(避免了空指標異常)。

2. 宣告與定義

  欄位/變數的定義Scala中使用var/val 變數/不變數名稱: 型別的方式進行定義,例如

var index1 : Int= 1
val index2 : Int= 1

  在Scala中宣告變數也可以不宣告變數的型別。 

  • 常量的宣告 val

  使用val來宣告一個常量。與java一樣,常量一次賦值不可修改。

val name : String="Yang"//這是完整的寫法,可以省略型別,如下所示:
val name="Yang"
name="Yang2"//會報錯reassignment to val
  • 變數的宣告 var
var name : String = "Yang" //這是完整的寫法,可以省略型別,如下所示:
//var name = "Yang" //變數或常量宣告時,型別可以省略,Scala內部機制會推斷。
name = "Yang2"//變數的值可以修改
  • 函式的宣告 def

  使用def關鍵字來宣告函式。例如:

複製程式碼
object HelloScala {
  def main(args: Array[String]): Unit = {
    println(f)
  }
  val a=1
  var b=2
  def f=a*b
}
複製程式碼

  def f=a*b;//只是定義a*b表示式的名字,並不求值,在使用的時候求值。這裡Scala已經推斷出了f函式的返回值型別了,因為ab都是Int,所以f也是Int。從控制檯可以看出這個效果:

  def f=a*b//如果寫成val f,這時會直接算出結果。這是定義函式和定義常量的區別。

3. 字串

  • 註釋

  單行註釋://

  • 單行字串

  同Java

  • 多行字串/多行註釋

  scala中還有類似於python的多行字串表示方式(三個單引號),用三個雙引號表示分隔符,如下:

val strs=”””
  多行字串的第一行   多行字串的第二行   多行字串的第三行”””
  • S字串

  S字串,可以往字串中傳變數。

  S字串可以呼叫變數/常量,在字串前面加s,在字串中使用变量/常量名的数学表达式,来调用变量。如图所示,字符串之前不写s,则原样输出。” role=”presentation”>/調s變數/常量名的數學表示式,來呼叫變數。如圖所示,字串之前不寫s,則原樣輸出。{變數/常量的數學表示式},如上圖所示對常量age進行計算。

  • F字串

  傳入的引數可以進行相應的格式的轉化。例如:

  先val height = 1.7//聲明瞭一個一位小數的常量身高。

  println(f”name” role=”presentation”>namenameheight%.2f meters tall”)//在字串前加f使用f字串的功能,包含了s字串的呼叫變數的功能,並且在變數名後面跟%格式來格式化變數。例如%s是表示字串,%.2f是精確到百分位。

  println(s”name” role=”presentation”>namenameheight%.2f meters tall”)//如果這裡使用s字串則只能包含s字串呼叫變數的功能,不能使用f字串格式化的功能。

  println(“name” role=”presentation”>namenameheight%.2f meters tall”)//如果不加s也不加f則原樣輸出。 

  • R字串

   R字串和Python中的raw字串是一樣的,在java中要原樣輸出一些帶\的字元,如\t、\n等需要在前面再加一個\轉義,不然就會輸出製表符、回車。比如\n就要寫成\\n,才能原樣輸出\n,但是加上raw則不需要。例如:

 

  注意r字串的使用是在字串前面加raw,而不是r。

4. 懶載入

  在Scala的底層有一個延遲執行功能,其核心是利用懶載入。如下圖懶載入常量:

  對比上面兩條命令的差異,可以發現沒有lazy的命令立即執行,並將1賦給常量x。而帶有lazy的命令沒有立即執行方法體,而是在後面val a=xl時才執行了方法體的內容。

  其中lazy是一個符號,表示懶載入;{println(“I’mtoolazy”);1},花括號是方法體,花括號中的分號是隔開符,用於隔開兩句話,方法體的最後一行作為方法的返回值。

  如上圖所示,定義函式的效果和懶載入方式的效果一樣,只有在呼叫的時候才會執行方法體。

三、Scala 函式

1. 函式的定義

  • 函式定義的一般形式

  如上圖所示,其中def關鍵字表示開始一個函式的定義;max是函式名;小括號中的x和y表示引數列表,用逗號隔開;小括號中的引數後面的:型別表示引數的型別;引數列表之後的:型別是函式的返回值型別;等號表示要返回值,如果沒有等號和返回值型別就表示不需要返回值,或返回值型別改為Unit也表示不需要返回值;花括號中的內容是方法體,方法體的最後一行將作為函式的返回值,這裡的最後一行是x或者y。

  • 函式定義的簡化形式

  省略return(實際已經簡化)。Scala中,可以不寫return,如果不寫return則自動將最後一行作為返回值,如果沒有返回值,則函式的返回型別為Unit,類似於Java中void。

  函式和變數一樣,可以推斷返回型別。所以上述函式可以簡寫成:

def max( x : Int, y : Int) = {if(x > y) x else y}

  這裡還可以進一步把方法體的花括號也省略,所以函式可以進一步簡化為:

def max(x : Int, y : Int) = if(x>y) x else y
  • 案例一(單個引數)
複製程式碼
object HelloScala {
  //  定義sayMyName方法,方法需要一個引數,型別是String,預設值是Jack。方法體前面沒有等號,就相當於沒有返回值,Unit
  def sayMyName(name : String = "張三"){
    println(name)
  }
  //函式的呼叫需要main函式
  def main(args: Array[String]) {
    sayMyName("李四")//如果沒有使用引數sayMyName()則使用預設值張三,如果使用引數"李四",則輸出李四
  }
}
複製程式碼
  • 案例二(多個引數,可變引數)

  多個相同型別的引數可以使用*表示,例如(k : Int*)表示多個Int型別的引數,具體數量不確定,類似於java中的可變引數。

複製程式碼
object HelloScala {
  def sumMoreParameter(k : Int*)={
    var sum=0
    for(i <- k){//使用foreach(<-)來遍歷元素k
      println(i)
      sum += i
    }
    sum
  }
  def main(args: Array[String]) {
    println(sumMoreParameter(3,5,4,6))//這裡傳遞的引數個數可變
  }
}
複製程式碼

  當然也可以定義引數個數確定的函式,如下:

object HelloScala {
  def add(a:Int,b:Int) = a+b//省略了方法體的花括號和方法返回值型別
  def main(args: Array[String]) {
    println(add(3,6))
  }
} 
  •  案例三(下劃線作引數)

  使用下劃線做引數名稱

複製程式碼
object HelloScala {
  def add(a:Int,b:Int) = a+b
  def add2 = add(_:Int,3)//呼叫add方法,使用下劃線是一個符號,可以不取變數名,引數的型別是Int
  def main(args: Array[String]) {
    println(add2(5))//這裡的結果和add(2,5)=7是一樣的。
  }
}
複製程式碼

2. 遞迴函式

  遞迴實際上就是方法自己調自己,也可以看成是遞推公式。以階乘為例:

  • 案例四(遞迴函式)
複製程式碼
object HelloScala {
  def fact(n: Int): Int = if (n <= 0) 1 else n * fact(n - 1)//注意這裡需要寫方法的返回值型別Int,因為遞迴的方法體裡面還有這個函式,所以無法對結果的型別進行推斷。
  def main(args: Array[String]) {
    println(fac(6))
  }
}
複製程式碼

3. 柯里化函式

  在電腦科學中,柯里化(Currying)是把接受多個引數的函式變換成接受一個單一引數(最初函式的第一個引數)的函式,並且返回接受餘下的引數且返回結果的新函式的技術。有時需要允許他人一會在你的函式上應用一些引數,然後又應用另外的一些引數。例如一個乘法函式,在一個場景需要選擇乘數,而另一個場景需要選擇被乘數。所以柯里化函式就是將多個引數分開寫,寫在不同的小括號裡,而不是在一個小括號中用逗號隔開。例如:

  • 案例五(柯里化函式)
object HelloScala {
  def mulitply(x:Int)(y:Int) = x*y
  def main(args: Array[String]) {
    println(mulitply(2)(4))
  }
}
複製程式碼
object HelloScala {
  def mulitply(x:Int)(y:Int) = x*y
  def mulitply2 = mulitply(2)_;//柯里化就是把引數可以分開來,把部分函式引數可以用下劃線來代替
  def main(args: Array[String]) {
    println(mulitply2(3))
  }
}
複製程式碼

4. 匿名函式

  • 匿名函式的概念

  匿名函式就是沒有名字的函式。例如 (x : Int, y : Int) => x * y 。這裡有一點要注意,如果在“=>”前加上了這個函式的返回型別,如:(x:Int, y : Int) : Int=> x * y,反而會報錯。原因是在一般情況下,Scala編譯器會自動推斷匿名函式引數的型別,所以可以省略返回型別,因為返回型別對於編譯器而言是多餘的。

  • 案例六(匿名函式,宣告方式
object HelloScala {
  val t = () => 123
  def main(args: Array[String]) {
    println(t())//直接呼叫t,注意要有小括號
  }
}

  匿名函式的標識就是=>,沒有方法名,只有一個小括號(這裡也沒有引數),方法體就是直接返回的123(是{123}的簡寫)。val t是將宣告的這個匿名函式物件付給了常量t。這裡看上去像是多此一舉,但是因為匿名函式往往是作為引數傳給一個函式的,所以匿名函式這樣的形式很有必要。

  • 案例七(匿名函式,做函式的引數)
複製程式碼
object HelloScala {
  val t = ()=>123//聲明瞭一個匿名函式物件付給了t
  def testfunc(c : ()=>Int ){
    println(c())
    333
  }
  def main(args: Array[String]) {
    println(testfunc(t))
  }
}
複製程式碼

  定義testfunc方法中需要一個引數c,其型別是()=>Int,而()=>Int是匿名函式型別的定義。這個表示式是指傳進來引數需要是一個匿名函式,該匿名函式沒有引數,返回值是Int,比如t就是這樣的匿名函式。

  在testfunc中是列印,在方法體裡面才真正的呼叫傳進來的函式;傳進來的時候只是傳進來了一個方法體,並沒有正真的呼叫。只有在裡面有了()時才真正的呼叫。

  println(testfunc(t))列印的結果有兩行,第一行是123、第二行是(),因為testfunc這個方法沒有返回值。如果將函式testfunc方法體前面加個等號就能打印出方法體最後一行(返回值)333。

  • 案例八(匿名函式,有參匿名函式的宣告)
object HelloScala {
  val b = (a:Int)=> a*2;//把一個能將傳進來的引數乘以2的匿名函式物件賦給b
  def main(args: Array[String]) {
    println(b(8))//列印的結果為16
  }
}
  • 案例九(匿名函式,有參匿名函式做引數)
複製程式碼
object HelloScala {
  def testf1(t: (Int,Int)=>Int )={
    println(t(15,15));
  }
  def main(args: Array[String]) {
    testf1((a:Int,b:Int)=>{println(a*b);a*b})//列印的結果為兩行225
  }
}
複製程式碼

  定義的一個以有參匿名函式作為引數的函式testf1,其引數名是t,引數型別是(Int,Int)=>Int這樣的匿名函式,它需要兩個Int型別的引數經過相應的轉化,轉為一個Int型別的返回值。

  t(15,15)這方法體裡才真正的呼叫匿名函式t,這裡的引數是寫死的,即在testf1方法裡才有真正的資料。但是真正對資料的操作是交給匿名函式的,這就體現了函數語言程式設計的特點。

5. 巢狀函式

  巢狀函式可以認為是複合函式,是def了的一個函式中又def了一個函式。 例如:

  • 案例十(巢狀函式)
複製程式碼
object HelloScala {
  //定義一個函式f=(x+y)*z
  def f(x:Int, y:Int ,z:Int) : Int = {
    //針對需求,要定義個兩個數相乘的函式g=a*b,相當於複合函式。
    def g(a:Int, b:Int):Int = {
      a*b
    }
    g((x+y),z)
  }
  def main(args: Array[String]) {
    println(f(2,3,5))
  }
} 
複製程式碼

6. 迴圈函式

  和Java的類似,Scala有foreach迴圈。

  • 案例十一(foreach迴圈)
複製程式碼
object HelloScala {
  //定義for_test1方法,使用for迴圈輸出1-50的整數
  def  for_test1() : Unit = {
    //"<-"這個符號表示foreach,使用"to"則包含末尾(閉區間),如果是until則不包含(左閉右開)。這裡的to是Scala內建的一個方法。
    for(i <- 1 to 50 ){ //可以從原始碼看到to是RichInt型別的方法
      println(i)
    }
  }
  def main(args: Array[String]): Unit = {
    for_test1()
  }
}
複製程式碼
  • 案例十二(foreach迴圈嵌入條件判斷)
複製程式碼
object HelloScala {
  //列印1000以內(不含),可以被3整除的偶數。
  def  for_test2() = {
    //可以直接在for括號裡面新增if過濾條件,比Java更簡潔。多個條件使用分號隔開
    for(i <- 0 until 1000 if (i % 2) == 0 ; if (i % 3) == 0 ){
      println("I: "+i)
    }
  }
  def main(args: Array[String]) {
    for_test2()
  }
}
複製程式碼

7. 分支函式

  和Java的switch…case…default分支類似,Scala有match…case…case_結構。

複製程式碼
object HelloScala {
  def testmatch(n:Int)={
    n match {
      case 1 => {println("是1") ;n}//"=>"表示有匿名函式,如果與1匹配上就走這個方法體
      //        break;在Scala中不需要寫break,也不能寫break,Scala中沒有break關鍵字。
      case 2 => println("是2") ;n//方法體的花括號可以省略。
      case _ => println("其他") ; "others" //case _:default
    }
  }
  def main(args: Array[String]) {
    println(testmatch(1))//結果為是1 \n 1
    println(testmatch(0))//結果為其他 \n others
  }
}
複製程式碼