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安裝與開發工具

  Scala版本使用Scala-2.10.x

  JDK使用jdk-1.8。

  開發工具使用Intellij IDEA-2017.3.5

二、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,則原樣輸出。${變量/常量的數學表達式},如上圖所示對常量age進行計算。

  • F字符串

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

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

  println(f"$name%s is $height%.2f meters tall")//在字符串前加f使用f字符串的功能,包含了s字符串的調用變量的功能,並且在變量名后面跟%格式來格式化變量。例如%s是表示字符串,%.2f是精確到百分位。

  println(s"$name%s is $height%.2f meters tall")//如果這里使用s字符串則只能包含s字符串調用變量的功能,不能使用f字符串格式化的功能。

  println("$name%s is $height%.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
  }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM