本文較為詳細地介紹了Scala的理論,並結合相應的代碼進行解讀與實踐。文章內容主要包含Scala的簡介,數據類型,函數(函數定義、匿名函數、嵌套函數、迴圈語句等),集合(List、Tuple、Map),伴生對象,trait,Actor,隱式轉換,JDBC等。 ...
目 錄
一、Scala概述
二、Scala數據類型
三、Scala函數
四、Scala集合
五、Scala伴生對象
六、Scala trait
七、Actor
八、隱式轉換與隱式參數
九、Scala JDBC
由於整理的篇幅較長,所以文章計劃分三次發佈。第一部分的內容請轉至系列(一)。
四、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。
3. 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) } }