Scala語言


一、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來表示缺少某值的做法更加安全(避免了空指針異常)。

  1. 聲明與定義
      字段/變量的定義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=ab
}
復制代碼
  def f=a
b;//只是定義a*b表達式的名字,並不求值,在使用的時候求值。這里Scala已經推斷出了f函數的返回值類型了,因為a和b都是Int,所以f也是Int。從控制台可以看出這個效果:

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

  1. 字符串
    注釋
      單行注釋://

單行字符串
  同Java

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

val strs=”””
  多行字符串的第一行

  多行字符串的第二行

  多行字符串的第三行”””
S字符串
  S字符串,可以往字符串中傳變量。

  S字符串可以調用變量/常量,在字符串前面加s,在字符串中使用變量/常量名的數學表達式,來調用變量。如圖所示,字符串之前不寫s,則原樣輸出。{變量/常量的數學表達式},如上圖所示對常量age進行計算。

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

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

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

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

  println("nameheight%.2f meters tall")//如果不加s也不加f則原樣輸出。

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

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

  1. 懶加載
      在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) = xy
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)=> a2;//把一個能將傳進來的參數乘以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方法里才有真正的數據。但是真正對數據的操作是交給匿名函數的,這就體現了函數式編程的特點。

  1. 嵌套函數
      嵌套函數可以認為是復合函數,是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
}
}

四、Scala集合

  1. Scala集合概述
      Scala對集合的操作就是Spark程序的實現方式。Spark中有一個RDD(Resilience彈性的、Distributed分布式、DataSet數據集),spark的程序都是將源數據加載過來變成一個RDD,然后每一步操作都是集合的元素進行操作。對於Spark來說是分布式的操作,但是對於寫程序來說無需考慮分布式,只需考慮對集合元素的操作。Scala的集合操作是針對單機的,Spark是針對分布式的,但是代碼都類似。

  2. List集合
      這里舉例創建集合和獲取集合元素的同時也有一些常用的集合操作函數。

創建List集合
   在Scala中創建一個集合不需要new關鍵字。

object TestCollection {
val list =List(1,4,6,4,1)
}
獲取集合的元素
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
println("獲取集合的第2個元素:"+list(1))//集合的下標從0開始
}
}
  使用list(),括號中傳入集合元素的位置來獲取集合元素。

map函數
  map函數的本質就是使用匿名函數對集合中每一個元素做同樣的操作。

復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
val b= list.map(a=>{println(a+"-----");a+1})
val c= list.map(_+1)
println(b)
println(c)
}
}
復制代碼
  list.map是集合list調用map方法,map方法對集合的每個元素進行操作,具體的操作由匿名函數定義。第一個map函數中的a代表集合List的每一個元素,作為匿名函數的參數,執行方法體打印,然后返回最后一行a+2賦給新的集合相應位置的元素。

  list.map(_+1))//這樣寫是上式的簡寫形式,下划線代表集合的每一個元素。

"+:"和":+"函數向集合添加元素
復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
val list2=list.+:("楊輝三角")
val list3=list.:+("楊輝三角")
def main(args: Array[String]): Unit = {
println(list2)
println(list3)
}
}
復制代碼
  +: 在集合第一個位置添加元素;:+ 在集合最后一個位置添加元素。運行結果如下:

  List(楊輝三角, 1, 4, 6, 4, 1)
  List(1, 4, 6, 4, 1, 楊輝三角)

foreach函數遍歷輸出
  foreach和map很相似,都是對集合的每一個元素做相應的操作,只是map會返回值給集合。如果要打印結果一般用foreach。

復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
val list2=list.+:("楊輝三角")
def main(args: Array[String]): Unit = {
list2.foreach(i => print("---"+i))//是對集合list2中的每一個元素遍歷,i表示集合中的每一個元素。
list2.foreach(i => {val j = i + "s"; print("---"+ j)})//可以對集合中的元素先進行有關操作
list2.foreach(print _)//對集合遍歷輸出可以簡化為此式
}
}
復制代碼
distinct函數去重
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
list.distinct.foreach(print _)
}
}
  輸出結果為:146

slice函數截取集合
  slice函數需要兩個參數,第一個參數表示從該下標開始截取,第二個參數表示截取到該下標(不包含)。

object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
print(list.slice(0,3))
}
}
  結果為:List(1, 4, 6)

for循環遍歷集合
復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
for(i <- list){
print(i)
}
}
}
復制代碼
length函數獲取集合長度
復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
for(i <- 0.to(list.length-1)){
print(list(i))
}
}
}
復制代碼
"/:"函數
復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]) {
//list./:是調用./方法,它是一個柯里化函數,其中(100)是第一個參數,({(sum,num)=>print(sum+"--"+num+" ");sum-num})是第二個參數。
println(list./:(100)({
(sum,num)=>print(sum+"--"+num+" ");//函數/:的第二個參數——匿名函數需要兩個參數,匿名函數第一個參數為/:函數的第一個參數,匿名函數的返回值類型和/:函數的第一個參數類型一致
sum+num//這里的匿名函數實際上是(sum,num)=>sum-num,就是傳入兩個參數sum和num,返回sum+num,返回值的類型顯然和sum的類型一樣。/:方法詳見源碼解讀。
}));//返回結果為100--1 101--4 105--6 111--4 115--1 116
}
}
復制代碼
復制代碼
//源碼片段:
def /:[B](z: B)(op: (B, A) => B): B = foldLeft(z)(op)

def foldLeft[B](z: B)(op: (B, A) => B): B = {
var result = z
this foreach (x => result = op(result, x))
result
}
復制代碼
  源碼解讀:如下代碼是/:函數的源碼,可見/:是個柯里化函數。其中,[B]是函數/:的泛型;(z: B)是第一個參數,其類型為泛型[B];(op: (B, A) => B)是第二個參數,它是一個匿名函數op,它需要兩個參數(B, A),能返回B類型的值。最后的:B是函數/:的返回值類型。/:(z)(op)=foldLeft(z)(op)。

  再看foldLeft函數,也是一個柯里化函數,需要兩個參數,參數類型和/:的參數類型一致。其方法體可見,首先將傳入的第一個B類型的參數z賦值給變量result,然后調用該方法的當前對象(如集合List對象)使用foreach(這里的this foreach和this.foreach是一樣的道理)方法遍歷當前對象中的所有元素,其元素x的類型就是匿名函數的第二個參數的類型A,這里調用匿名函數op,以result和x為參數,其返回結果賦值給result,通過多次調用匿名函數循環集合的所有元素,最后返回result,作為函數foldLeft的返回值,也就是函數/:的返回值。

reduce函數
  reduce函數和/:函數很類似,使用的頻率很高。

復制代碼
object TestCollection {
val list =List(1,4,6,4,1)
def main(args: Array[String]): Unit = {
//reduce函數,需要一個匿名函數做參數,此匿名函數的類型是(A1,A1)=>A1,匿名函數第一次的參數是前兩個元素;之后,第一個參數上一次的匿名函數返回值,第二個參數是依次位置的集合元素值。
println(list.reduce((a: Int, b: Int) =>{println(a + "---" + b) ; a+b}))//最終結果是所有元素的和16
//上式求所有元素的和和以簡化為下面的形式
println(list.reduce(+))
}
}
復制代碼
復制代碼
//源碼片段:
def reduce[A1 >: A](op: (A1, A1) => A1): A1 = reduceLeft(op)
def reduceLeft[B >: A](op: (B, A) => B): B = {
if (isEmpty)
throw new UnsupportedOperationException("empty.reduceLeft")

var first = true
var acc: B = 0.asInstanceOf[B]

for (x <- self) {
if (first) {
acc = x
first = false
}
else acc = op(acc, x)
}
acc
}
復制代碼
  源碼解讀:reduce函數最終是要調用reduceLeft函數,顧名思義是需要從左側開始。reduceLeft函數需要一個匿名函數(op: (B, A) => B),返回類型是B,和傳入的第一個參數一樣。if (isEmpty)如果集合為空,拋出異常。0.asInstanceOf[B],B是一個泛型,0是int類型,0.asInstanceOf[B]意為將0轉為B這個泛型類型。在else acc = op(acc, x)中才開始調用傳入的匿名函數op。

  1. Tuple元組
    創建元組
      在Scala中創建元組不需要關鍵字,只需要括號就行。它的特點是,定義了元組之后,元組的值不可以修改(和Python一致)。

object TestCollection {
val tuple =(1,4,6,4,1)
}
獲取元組元素
復制代碼
object TestCollection {
val tuple =(1,4,6,4,1)
def main(args: Array[String]): Unit = {
println(tuple.1)//元組取值是用".",不能像list集合一樣用括號
println(tuple._5)//Tuple元組下標從1開始。
}
}
復制代碼
4. Map集合
  Scala中的Map有兩種類型,一個是Mutable可以更改的,另一個是Immutable不可更改的。如果沒有導包直接寫Map的話是默認為Immutable的,如果要創建可以更改key的value值的Map集合需要導包,指定是Mutable的Map。

創建Map集合
復制代碼
object TestCollection {
//定義一個map集合,[String,Int]分別是鍵和值的泛型。
var map = Map[String, Int]("a" -> 1, "b" -> 2);//使用“->”來定義一對key value,每對key/value使用逗號隔開。
var map2 = Map[String, Int](("a", 3), ("b", 4));//也可以使用(key,value)的形式定義一對key/value,因為Map中的每一個元素都是一個元組。
}
復制代碼
獲取集合元素
object TestCollection {
var map = Map[String, Int]("a" -> 1, "b" -> 2);
def main(args: Array[String]) {
println(map("a"));//使用(鍵)來獲取對應的值
}
}
"+="函數添加集合元素
復制代碼
object TestCollection {
var map = Map[String, Int]("a" -> 1, "b" -> 2);
map += ("c" -> 3)
map += Tuple2.apply("d",4)
def main(args: Array[String]) {
println(map);//輸出結果為Map(a -> 1, b -> 2, c -> 3, d -> 4)
}
}
復制代碼
foreach函數遍歷集合
復制代碼
object TestCollection {
var map = Map[String, Int]("a" -> 1, "b" -> 2);
def main(args: Array[String]) {
map.foreach(kv=>{
println(kv+" "+kv._1+" "+kv._2)//這里的kv是集合m1的每一個元素,它是一組鍵值對,在Scala中是一個元組,所以要取得每一個元素的鍵和值可以使用元組的取值方法,kv._1獲得kv的鍵,kv._2獲得kv的值。
})//其結果為(a,1) a 1\n(b,2) b 2
}
}
復制代碼
keys迭代器
復制代碼
object TestCollection {
var map = Map[String, Int]("a" -> 1, "b" -> 2);
def main(args: Array[String]): Unit = {
map.keys.foreach(k => println(map(k)))//map.keys獲得map的所有keys,返回一個迭代器;然后可以使用foreach遍歷,也可以在通過鍵獲取值。
}
}
復制代碼
五、Scala伴生對象

  1. 伴生對象的概念
      所謂伴生,就是在語言層面上,把static成員和非static成員用不同的表達方式,class(非靜態成員)和object(靜態成員),但雙方具有相同的包名和命名(class_name和object_name可以完全一樣),編譯器會把他們編譯到一起。編譯會生成.class文件,編譯時會把名稱相同的class非靜態的和object靜態的編譯到一起。

  2. Object&Class
    案例一
    復制代碼
    class Test{
    var field = "field" //類的屬性
    def doSomeThing = println("do something")//類的方法,調用需要new 對象后才可以調用
    }

object Test{
val a = "a string" //伴生對象的屬性
def printAString = println(a)//這個方法是靜態的,可以使用Test.printString來調用。
}
復制代碼
  編譯這個文件,同樣生成兩個class,一個TEST.class和一個Test.class,這個Test.class叫做虛構類。

案例二(靜態方法和屬性)
復制代碼
class TestObject {
val str = "Good!"
def func() = {
println("Hello World!");
}
}

object TestObject {
val str= 100;
val single = new TestObject();
def func() = {//定義在object里是靜態方法
println("Hello Scala!");
}
/**
* main函數是static的,main函數如果定義在class中會當做普通函數,函數名為main而已。
*/
def main(args: Array[String]) {
//創建class的實例需要用new關鍵字
val t1 = new TestObject();
println(t1.str);//調用實例的str屬性
t1.func();//調用實例的func01函數

TestObject.func();//Object名.靜態方法名。
println(TestObject.str);//Object名.靜態屬性名。

}
}
復制代碼
  執行結果如下:

Good!
Hello World!
Hello Scala!
100
案例三(構造器)
復制代碼
class TestConstructor(val a: Int, val b: Int) {//class類后面的小括號,是默認的構造器
var x = a;//把參數a和b賦給變量
var y = b;

def this(xArg: Int) { //this也是構造器,在方法體里面要調用默認的構造器。
this(xArg, 123);
println("I'm this constructor");
}
}

object TestConstructor {//這個對象是伴生着這個類出來的,所以叫伴生對象
def main(args: Array[String]) {
val p1 = new TestConstructor(321);//使用 this(xArg: Int)構造器
println(p1.x)//321
println(p1.y)//123
val p2 = new TestConstructor(222, 333);//使用Point(val x: Int, val y: Int)構造器
println(p2.x)//222
println(p2.y)//333
}
}
復制代碼
六、Scala trait
  trait可以認為是一種特性,但是不等同於Java中的接口,因為Java接口中沒有實現的方法,train可以有實現了的方法(方法體),trait的作用在於提取封裝共性,供各種類型的類共同使用。用法詳見下面例子:

復制代碼
trait Listen {//和java的接口很像,但是不能new trait名。
val name: String //定義變量name
def listen() = {
println( name + " is listening")
}
}
trait Read {
val name: String
def read() = {
println(name + " is reading")
}
}
trait Speak {
val name: String
def speak() = {
println(name + " is speaking.")
}
}
class Human(val name: String) {//默認構造器需要一個屬性:名字
def speak() = {//方法
println("Look, "+name + " is speaking.")
}
}
class Animal(val name: String) {}//創建一個動物類,構造器也是一個參數名字。
//extends繼承Animal類。Dog(override val name: String),重寫name;with關鍵字可以加上trait特性。和Java中的繼承類,實現接口類似。
class Dog(override val name: String) extends Animal(name: String) with Speak with Listen with Read {
//重寫一個方法只需在方法def前面加關鍵字override。
override def toString(): String = "Hello, My name is " + name + "! "
}
object TestTrait {
def main(args: Array[String]) {
//創建Human類的對象,命名為張三。hi調用Human類的speak方法。
val h1 = new Human("張三")
h1.speak()
//創建Dog對象,調用了trait中listen方法和speak方法。
val dog = new Dog("八公")
dog.listen()
dog.speak()
//調用Dog類重寫了的toString方法。
println(dog)
}
}
Scala語言入門到高級視頻教程
網盤地址:https://pan.baidu.com/s/1kVZOEmr 密碼: 3z6z

Scala編程語言視頻教程
網盤地址:https://pan.baidu.com/s/1ghdAP83 密碼: mq6x


免責聲明!

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



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