工具篇-Scala基礎

1. Scala開發爲何須要Java環境?

Scala須要Java虛擬機把程序編譯成字節碼運行。html

2. Scala中全部類的超類及空返回值

Java中全部類的超類是Object,Scala中全部類的超類是Any;es6

Java中的空返回值爲Void,Scala中的空返回值是Unit。另外Scala還有Null,null,Nil,Nothing,None,Unit幾種空,具體講解能夠參見:http://www.javashuo.com/article/p-bzmvnpow-cg.html編程

3. Scala中的to和util

相同點:Scala中to和util都返回Range類型數組

不一樣點:返回的數據範圍不一樣,例如:網絡

1 val to1 = 1 to 10
2 val to2 = 1 to 10 by 2
3 val until1 = 1 until 10
4 val until2 = 1 until 10 by 2
5 println(to1) 6 println(to2) 7 println(until1) 8 println(until2)

輸出:app

1 Range(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 2 Range(1, 3, 5, 7, 9) 3 Range(1, 2, 3, 4, 5, 6, 7, 8, 9) 4 Range(1, 3, 5, 7, 9)

4. Scala中的Val和Var聲明的變量

Scala的類文件中不寫set和get方法,val聲明的變量爲不可變變量,默認只有get方法,var聲明的是可變變量,默認有set和get方法。編輯器

5. Scala中的方法和函數

Scala的方法跟Java中相似,它是類的一部分,好比Scala中的操做符(+,-等)都是方法,方法一般用def定義,定義格式以下:ide

1 def methodName ([參數1:參數類型1,參數2:參數類型2,...]) : [return type] = { 2  method body 3 return [expr] 4 }

其中,方法能夠沒有參數,返回值也能夠是空Unit,相似於Java中的Void;函數

而函數是一個繼承了Trait類的對象,函數必須有參數,函數一般用val定義,定義格式以下:工具

1 val functionName = (參數1:參數類型1,參數2:參數類型2,...) => functionBody

另外,方法能夠轉爲函數:

1 f1 = m1 _ 

6. 匿名函數

什麼是:一個函數只使用一次,並無屢次調用,即沒有名字的話就是匿名函數。

應用場景:

  • 賦給變量;
  • 做爲參數傳給函數。

舉例:

1 //匿名函數的使用場景1,做爲參數,傳入給高階函數
2 Array(3,2,1,5).map{(x: Int) => x + 2} 3 
4 //匿名函數的使用場景2,直接賦值給變量
5 val kafkaName = (name: String) => println("--kafka-->" + name)

7. 高階函數

使用函數值做爲參數,或者返回值爲函數值的「函數」和「方法」,均稱之爲「高階函數」。例如Scala集合類(collections)的高階函數map:

 1 (1 to 9).map("^" * _).foreach(println _)  2 /**打印三角形  3 ^  4 ^^  5 ^^^  6 ^^^^  7 ^^^^^  8 ^^^^^^  9 ^^^^^^^ 10 ^^^^^^^^ 11 ^^^^^^^^^ 12 */

返回值爲函數值的例子能夠參考:高階函數

8. 柯里化

官方定義:Currying指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數爲參數的函數。這裏的柯里化函數實際上也是一個高階函數。

例如對於原始函數(不知道爲啥叫函數)

1 def add(x:Int,y:Int)=x+y

有變形

1 def add(x:Int)(y:Int) = x + y

實際上給定一個參數 x,返回一個函數類型的值,第二次使用參數y調用這個函數類型的值。

1 def add(x:Int)=(y:Int)=>x+y

這樣咱們就能夠定義多個函數,例如給定x=1

1 scala> val second=add(1) 2 second: Int => Int = <function1>
3 
4 scala> second(2) 5 res1: Int = 3

再好比給定x=2

1  scala> val third=add(2) 2  third: Int => Int = <function1>
3  
4  scala> third(2) 5  ress: Int = 4

9. Scala中的for循環和yield

for循環和yield使用的原理是:

  • 每一次for循環,yield 會產生一個值記錄下來 (內部實現上,像是一個緩衝區),當循環結束後, 會返回全部yield值組成的集合;
  • 返回集合的類型與被遍歷的集合類型是一致的。

例1:

1 scala> for (e <- a) yield e * 2
2 res6: Array[Int] = Array(2, 4, 6, 8, 10)

固然,也能夠在for循環中加上'if'表達式,如例2:

1 scala> val a = Array(1, 2, 3, 4, 5) 2 a: Array[Int] = Array(1, 2, 3, 4, 5) 3 4 scala> for (e <- a if e > 2) yield e 5 res1: Array[Int] = Array(3, 4, 5)

10. Scala中偏函數

偏函數只接受輸入參數的子集,例以下面collect函數的例子,Int=>41 / i這個匿名函數只針對非零輸入41纔有定義,而對0沒有定義。

1 scala> List(41, 0) collect { case i: Int ⇒ 41 / i } 2 res1: List[Int] = List(1)

其實偏函數能夠顯式地定義爲:

1 val fraction = new PartialFunction[Int, Int] { 2     def apply(d: Int) = 42 / d 3     def isDefinedAt(d: Int) = d != 0
4 }

isDefinedAt方法會自動過濾傳入參數,大致是這麼個意思,具體可參考:https://www.cnblogs.com/daoyou/articles/3894182.html

11. 單例對象

若是某個方法或域與某個類的特定對象無關,而是隻與類相關,把它歸於單例對象singleton objects,即由object關鍵字聲明的類型:

1 object X { 2     // 至關於Java中靜態代碼塊
3     val n = 2; 4     // 至關於Java中靜態方法
5     def f = n * 10
6 }

由於它是單例對象,因此在使用的時候,不須要New,直接調X.f便可獲得返回值20,對Java程序猿來講,這就是static的做用,在Scala中,全部本來Java中用static修飾的屬性和方法或者代碼塊都應該移到單例對象裏, 因此scala建立一個單例對象(包含main方法)來爲程序的執行提供入口點, 若是單例對象繼承App,App內部已經有一個main方法,所以不用main也能夠執行:

1 object ObjectDemo extends App { 2     println("hello") 3 }

單例對象能夠擴展類和接口。單例對象有些時候是做爲伴隨對象companion object出現的,即它是另外一個同名類的伴隨對象

12. 伴生對象

當一個單例對象與類具備相同的名稱時,這個單例對象被稱做是這個類的伴生對象,一樣,這個類被稱做是這個單例對象的伴生類
類和它的伴生對象能夠互相訪問私有特性,類和它的伴生對象必須定義在同一個源文件中,若是Scala中不用New直接調用,通常是調用了類的伴生對象,經典Demo以下:

 1 /**
 2  * Created by root  3  * Description : 伴生對象和伴生類  4  *  5  * 伴生類ObjectAndClassTest的構造函數定義爲private,雖然這不是必須的,卻能夠有效防止外部實例化ObjectAndClassTest類,使得ObjectAndClassTest類只能供對應伴生對象使用;  6  * 每一個類均可以有伴生對象,伴生類與伴生對象寫在同一個文件中;  7  * 在伴生類中,能夠訪問伴生對象的private字段ObjectAndClassTest.specialty  8  * 而在伴生對象中,也能夠訪問伴生類的private方法:objectAndClassTest.getSpecialtyName()  9  * 最後,在外部不用實例化,直接經過伴生對象訪問方法:ObjectAndClassTest.printSpectalty() 10  * 11   */
12 class  ObjectAndClassTest private(val name:String) { 13 
14   private def getSpecialtyName() = "name = " + name + ";specialty = " + ObjectAndClassTest.specialty 15 } 16 
17 object ObjectAndClassTest{ 18     private val specialty = "software development"
19     private val objectAndClassTest = new ObjectAndClassTest("xiaoming") 20     def printSpectalty() = println(objectAndClassTest.getSpecialtyName()) 21 
22 } 23 
24 
25 object Test{ 26   def main(args: Array[String]): Unit = { 27  ObjectAndClassTest.printSpectalty() 28  } 29 }

另外,當一個單例對象沒有與類具備相同的名稱,這個單例對象被稱做是獨立對象
獨立對象通常做爲Scala應用的入口點,或相關功能方法的工具類。

13. Apply方法

一般,在一個類的伴生對象中定義Apply方法,在生成這個類的對象時,不用New關鍵字默認調用Apply方法,好比Map、Array等集合類。Apply方法能夠理解爲注入,在構造類的時候調用,主要用來解決複雜對象的初始化問題,Scala的Apply有兩種方式,一種是伴生類的Apply,另外一種是伴生對象的Apply。示例參考Scala基礎-對象:單例對象、伴生對象、apply方法,以下:

 1 class ApplyTest {  2   def apply() = println("這是伴生類的apply方法")  3 }  4 object ApplyTest {  5   def apply() = {  6     println("這是伴生對象的apply方法")  7      new ApplyTest  8  }  9 } 10 // 獨立對象,做爲程序入口
11 object ApplyInit{ 12   def main(args: Array[String]): Unit = { 13     // 經過伴生對象的apply方法,進行實例化
14     val applyTest=ApplyTest() 15     // 這是伴生類的apply方法
16  applyTest() 17  } 18 }

結果:

1 這是伴生對象的apply方法 2 這是伴生類的apply方法

14. Unapply方法

unapply方法的入參對應你想要進行匹配的對象,出參則是解構後的元素。好比,求一個數的開方根

1 object Square { 2   def unapply(z: Double): Option[Double] = Some(math.sqrt(z)) 3 }

咱們不用顯式的調用,在match case中使用時編輯器會幫咱們調用

1 val number: Double = 36.0
2 number match { 3   case Square(n) => println(s"square root of $number is $n") 4   case _ => println("nothing matched") 5 }

上邊的大體步驟是

一、調用unapply,傳入number

二、接收返回值並判斷返回值是None,仍是Some

三、若是是Some,則將其解開,並將其中的值賦值給n(就是case Square(n)中的n)

固然,Unapplyseq和Unapply差很少,也減小了咱們Java編程裏必須寫的代碼,定義以下

1 object Names { 2   def unapplySeq(str: String): Option[Seq[String]] = { 3     if (str.contains(",")) Some(str.split(",")) 4     else None 5  } 6 }

15. Lazy關鍵字

特性:一個變量聲明爲lazy,則只有第一次調用時纔會進行初始化,以後不再會被計算,所以lazy修飾的變量必須同時是val修飾的不可變變量;

使用場景:

  • 某些變量初始化比較耗時,則可使用lazy;如網絡IO,磁盤IO等,marathon源碼與spark源碼中大量使用了這種特性;
  • 構造順序問題,好比:
1 trait Person{ 2  val name: String 3   val nameLen = name.length 4 } 5 
6 class Student extends Person{ 7   override val name: String = "Tom"
8 }

在main函數中new一個Student,會報空指針異常,這是由於父類的constructor先與子類執行,那麼在執行val nameLen = name.length的時候name尚未被賦值,因此纔會致使空指針。解決辦法就是在nameLen前加lazy,延遲初始化。

1 lazy val nameLen = name.length

原理:Scala也是編譯成字節碼跑在Jvm上的,而Jvm的字節碼指令並無提供對lazy這種語義的支持,因此推斷lazy只是一個語法糖,就是編譯器在編譯期將變量的初始化過程替換爲Double Check Lock,相似Java中的懶漢式單例模式初始化。

16. case class

Scala中的case class和普通的class的區別是:

  • 初始化的時候能夠不用new,普通類必定須要加new;
  • toString的實現更漂亮;
  • 默認實現了equals 和hashCode
  • 默認是可序列化,也就是實現了Serializable ;
  • case class構造函數的參數是public,咱們能夠直接訪問類的成員;
  • 支持模式匹配;這是定義case class的惟一理由。

17. match case(模式匹配)

Scala的match case和Java中switch case有區別,Scala中模式匹配能夠匹配值、集合、類型以及有值和沒值,另外每一個分支不須要break,自動退出。

  • 變量值的匹配,好比字符串匹配
1 變量 match{ 2       case "aa" => println("0") 3       case "bb" => println("1") 4       case "cc" => println("2") 5       case _ => println("null")  //表示全部的都沒有匹配到執行這裏
6 }
  • 增長if守衛,進行雙重過濾
1 變量1 match{ 2        case "aa" => println("0") 3        case "bb" => println("1") 4        case "cc" => println("2") 5        case _ if 變量2=="dd" => println("3") 6        case _ => println("null")  //表示全部的都沒有匹配到執行這裏
7  }
  • 匹配類型
1 變量 match{ 2       case x:Int if(x>3) => println("Int") 3       case y:String  => println("String") 4       case z:Double => println("Double") 5       case flag:Boolean => println("Boolean") 6       case _ => println("null")  //表示全部的都沒有匹配到執行這裏
7 }

或者

1 try{ 2       println(3/0) 3 }catch { 4      case e:IOException  => println("IOException") 5      case e:Exception  => println("Exception") 6 }finally{ 7      println("程序結束!") 8 }
  • 變量賦值(對於_這種不知足前面分支的狀況,能夠對其進行賦值處理)
1 score match { 2         case "A"=>println("excellent") 3         case "B"=>println("good") 4         case "C"=>println("soso") 5         case _score =>println("you need work harder,your score only "+_score)  //變量賦值
6 }
  • 匹配數組和序列
 1 //匹配數組
 2 val arr3=Array(0,1,2,3,4,5)  3 arr3 match{  4       case Array(0,x,y) => println(x+"--"+y) //匹配以0開頭,後面兩個元素的數組
 5       case Array(0) => println("only 0")     //匹配只有一個元素的數組
 6       case Array(0,_*) => println("many") //匹配以0開頭的數組,_表示任意元素,*表示一到多個元素
 7 }  8 
 9 //匹配序列
10 val lst1=List(3,1,-1) 11 lst1 match{ 12       case 0::Nil => println("only 0")    //匹配只有一個元素0的序列
13       case x::y::Nil =>println(x+"--"+y) //匹配有兩個元素的序列
14       case 0::tail => println("0 is head ") //匹配以0開頭的序列
15       case _ => println("nothing ") 16 }
  • case class
 1 變量 match {  2       case SubmitTask(id,name) => println(id,name)  //這裏能將樣例類中的參數提取出來,是是由於有unapply方法
 3       case HeartBeat(time) => println(time)  4       case CheckTimeOutTask => println("CheckTimeOutTask")  5 }  6 
 7 //多例樣例類
 8 case class SubmitTask(id: String, name: String)  9 //多例樣例類
10 case class HeartBeat(time: Long) 11 //單例樣例類
12 case object CheckTimeOutTask
  • Option(有值是Some,沒值是None)
1 grade match { 2         case Some(grade) => println("your grade is " + grade) 3         case None => println("Sorry, your grade information is not in the system") 4 }

18. 隱式轉換

  • 何時調用隱式轉換

調用一個方法時會首先從全局中尋找,當尋找不到或者方法的參數類型不匹配時才使用隱式轉換,隱式轉換隻能定義在Object中。

直接上例子:1 to 10能夠寫成1.to(10),咱們知道1是Int類型的變量,因此按道理Int類中會有一個to的方法,但事實上咱們在Int類型中根本就沒有找到to方法,爲啥還能正常運行呢?

在Scala交互命令行中執行命令:implicits -v,能夠查看系統爲咱們自動引入的各類默認隱式轉換,其中就有一個

1 implicit def intWrapper(x: Int): runtime.RichInt

這個隱式方法可以把Int類型的變量轉換成runtime.RichInt變量,而RichInt 中確實存在 to 方法

能夠看到隱式轉換很神奇,但我的水平有限我仍是以爲能少用就少用。

19. Scala中Array、ArrayBuffer

Scala中Array與Java中相似,也是長度不可改變的數組。此外,Scala數組的底層其實是Java數組。例如字符串、整型數組在底層就是Java的String[]、Int[]。

1 // 數組初始化後,長度就固定下來了
2 val a = new Array[Int](10) 3 a(0) = 1
4 
5 // 能夠直接使用Array()建立數組,元素類型自動推斷
6 val a = Array("leo", 30) 7 a(0) = "hi"

Array中的一些操做以下:

 1 // 數組元素求和
 2 val a = Array(1, 2, 3, 4, 5)  3 val sum = a.sum  4 // 獲取數組最大值
 5 val max = a.max  6 // 對數組進行排序
 7 scala.util.Sorting.quickSort(a)  8 // 獲取數組中全部元素內容
 9 a.mkString 10 a.mkString(", ") 11 a.mkString("<", ",", ">") 12 // toString函數
13 a.toString 14 b.toString

在Scala中,若是須要相似於Java中的ArrayList這種長度可變的集合類,則可使用ArrayBuffer

 1 // 若是不想每次都使用全限定名,則能夠預先導入ArrayBuffer類
 2 import scala.collection.mutable.ArrayBuffer  3 // 使用ArrayBuffer()的方式能夠建立一個空的ArrayBuffer
 4 val b = ArrayBuffer[Int]()  5 // 使用+=操做符,能夠添加一個元素,或者多個元素  6 // 這個語法必需要謹記在心!由於spark源碼裏大量使用了這種集合操做語法!
 7 b += 1
 8 b += (2, 3, 4, 5)  9 // 使用++=操做符,能夠添加其餘集合中的全部元素
10 b ++= Array(6, 7, 8, 9, 10) 11 // 使用trimEnd()函數,能夠從尾部截斷指定個數的元素
12 b.trimEnd(5) 13 // 使用insert()函數能夠在指定位置插入元素 14 // 可是這種操做效率很低,由於須要移動指定位置後的全部元素
15 b.insert(5, 6) 16 b.insert(6, 7, 8, 9, 10) 17 // 使用remove()函數能夠移除指定位置的元素
18 b.remove(1) 19 b.remove(1, 3) 20 // Array與ArrayBuffer能夠互相進行轉換
21 b.toArray 22 a.toBuffer

Array和ArrayBuffer的遍歷

 1 // 使用for循環和until遍歷Array / ArrayBuffer  2 // 使until是RichInt提供的函數
 3 for (i <- 0 until b.length)  4  println(b(i))  5 // 跳躍遍歷Array / ArrayBuffer
 6 for(i <- 0 until (b.length, 2))  7  println(b(i))  8 // 從尾部遍歷Array / ArrayBuffer
 9 for(i <- (0 until b.length).reverse) 10  println(b(i)) 11 // 使用「加強for循環」遍歷Array / ArrayBuffer
12 for (e <- b) 13   println(e)

這一部分具體能夠參考:https://www.cnblogs.com/HeQiangJava/p/6706951.html  

20. 字符串插值

Scala爲咱們提供了三種字符串插值的方式,分別是 s, f 和  raw。它們都是定義在 StringContext 中的方法。

  • s字符串插值器

能夠解析字符串中的變量,能夠調用方法,還能進行計算。實際調用的是StringContext中的s方法。

 1 val name = "Unmi"
 2 println(s"Hello $name")   //Hello Unmi
 3 println(s"Hello ${name}qq) //Hello Unmiqq 界定變量用大括號{},s"Hello $nameqq" 會試圖解析變量nameqq
 4 println(s"1 + 1 = ${1 + 1} //1 + 1 = 2,能計算值
 5  
 6 class Person(val name: String){  7     def say(what: String) = s"say $what"
 8 }  9 val person = new Person("Unmi") 10 println(s"Hello ${person.name}, ${person.say(person.name)}")   //Hello Unmi, say Unmi, 這個比較複雜

用大括號能夠界定變量,{}裏能夠求值,還可使用屬性和調用方法。

  • f字符串插值器

它除s的功能外(不指定格式就和s同樣),還能進行格式化輸出,在變量後用%指定輸出格式。實際調用的是StringContext中的f方法。

1 val height = 1.9d
2 val name = "James"
3 println(f"$name%s is $height%2.2f meters tall") // James is 1.90 meters tall
  • raw 字符串插值

raw 能讓字符串原本來本的輸出來,而不是產生控制效果,如對\n,\t 等的輸出。實際調用的是StringContext中的raw方法。

1 scala> println("a\nb\tc") 2 a 3 b c 4  
5 scala> println(raw"a\nb\tc") 6 a\nb\tc

 

Apply

相關文章
相關標籤/搜索