靜態關鍵字不是面向對象中的語法,scala中函數可理解爲java中的靜態方法java
scala是徹底面向函數的編程語言,scala中的類其實也是一個函數linux
這裏的函數相似於java的靜態方法,體現的是功能的封裝算法
java中方法的聲明 public static void(返回值類型) 方法名 (參數列表) throws 異常列表{ 方法體 return "123" } java中方法的調用 new User().test() scala中函數的聲明: scala中沒有public的概念,也沒有靜態的概念,因此爲了和屬性區分開,使用def關鍵字聲明 scala中返回值類型須要放置在方法名的後面,使用冒號分隔 scala中參數列表的聲明和java一致,但參數聲明有變化: 參數名:參數類型。... scala中沒有throws關鍵字,因此函數根本就不須要拋出異常 scala中函數也能夠返回結果 // scala中最基本的函數聲明 def test( name : String, age : Int ) : String = { // 方法體 println("name = " + name + ", age = " + age) return "123" } // 調用函數 test("zhangsan", 12)
聲明編程
def main(args: Array[String]): Unit = { def hello(name: String): Unit = { println(s"Hello $name") } hello("smile") //1.無參無返回值加Unit def f1(): Unit = { println("無參無返回值") } //f1() //2.有1個參,無返回值 def f2(name: String): Unit = { println("有1個參,無返回值") } //f2("kris") //3.多個參,無返回值 def f3(name: String, age: Int): Unit = { println("多個參,無返回值") } //f3("kris", 22) //4.無參有返回值 聲明一個變量接收函數返回值或者直接打印 def f4(): String = { return "abc" } val result: String = f4() println(result) //println(f4()) //5.有參,有返回值 def f5(name: String): String = { return name } println(f5("kris")) }
def fun1(): String = { return "smile" } //println(fun1()) // 方法中若是使用了return關鍵字,那麼方法必定要有返回值類型 // 若是不使用return關鍵字,那麼返回值類型能夠省略 // 方法能夠自動推斷方法返回值類型,根據方法體邏輯中的最後一行代碼的結果來推斷,因此能夠把return省略掉 def fun2() = { "alex" } //println(fun2()) def fun3() : String = { "kris" } //println(fun3()) ////////////////////////////////return////////////////////////////////////// // 函數若是明確了返回值爲Unit,那麼方法體中若是使用return不起做用 def fun4(): Unit = { return "abc" } //println(fun4()) //() //若是明確方法沒有返回值,也不但願自動推斷,那麼能夠省略 = 等號 def fun5(){ "哈哈" } //println(fun5()) //() // 若是函數沒有參數列表,那麼參數小括號能夠省略,而且調用時,不須要使用小括號 def fun6{ println("省略參數列表") } //fun6 //println(fun6) // 若是方法體的邏輯代碼只有一行,那麼花括號能夠省略,爲了區分方法名和方法體,須要使用等號鏈接 def fun7 = println("Hi") //fun7 //println(fun7) // 之因此使用def關鍵字來聲明函數,就是爲了在某些場合和變量區分開 def fun8 = "kris" println(fun8) // 匿名函數 ()->{}
// 在參數類型的後面增長星號,表示可變參數 // 可變參數必須放置在參數列表的後面 def func1(age: Int, name : String* ): Unit ={ println(age + ",\t" + name) } //func1(22, "alex", " smile", "kris") //22, WrappedArray(alex, smile, kris) // 給參數默認值,若是調用方法時,沒有傳遞指定的參數,那麼會採用默認值代替 // 若是參數了指定的參數,那麼會將默認值覆蓋掉 def func2(name : String = "kk", age: Int): Unit ={ println(s"$name, $age") } func2("kris", 22) //kris, 22 def func3(name : String, age : Int): Unit ={ println(s"$name, $age") } // 方法在調用時,傳遞的參數從左到右依次進行賦值 // 帶名參數 func3(age = 21, name = "kk")
(參數類型)=>(返回值類型)windows
// 將函數當成參數 def func1(i: Int): Int ={ 20 + i } def func2(i: Int): Unit ={ 11 + i } def func3(i: Int): Unit ={ println("i:" + i) } // 將符合要求的函數(輸入Int,輸出Int)做爲參數傳遞給另一個函數 def func01(f: (Int) => Int): Unit ={ println(f(3)) } func01(func1)//23
柯里化(Currying)是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數且返回結果的新函數的技術閉包
def test( a : Int, b:Int, c:Int )--->> def test( a:Int )(b:Int)(c:Int)
實現原理其實就是將每個須要多個條件(參數)的複雜邏輯函數進行拆分,轉換爲單一條件(參數)的簡單函數,經過簡單函數的組合解決複雜的邏輯操做,相似於JavaEE中三層架構的感受架構
def test( a:Int )((Int, Int)=>Int)(b:Int)
在柯里化的實現中,使用了閉包的功能,所謂的閉包就是將函數外的局部變量包含到函數內部的一種處理機制,這樣能夠避免函數外的變量被回收致使程序異常的狀況。app
def func3(i: Int): Unit ={ println("i:" + i) } def func02() = { func3 _ //函數當成返回值,把方法返回而不是結果返回,加_ } func02()(4) //i:4 // 函數柯里化(主要目的是完成隱式轉換);上面方式有點亂,聲明的時候能夠直接加兩個括號!跟上邊的方式是同樣的;這種簡化就是柯里化,把複雜的業務邏輯拆成一段一段,它不是一個函數; def func03()(i: Int): Unit ={ println(i) } func03()(4) //4
閉包
// 改變局部變量的生命週期,將變量包含到當前函數的內部,造成了一個閉包的效果;執行完f4,它的局部變量i就無效了(彈棧了),f5把它包進去 // scala中函數能夠聲明在任意的地方 def func04(i: Int)={ def func05(j: Int): Unit ={ println(i + j) } func05 _ } func04(10)(20) //30 def func06(i: Int)(j: Int): Unit ={ //按柯里化方式聲明,柯里化底層就是閉包 println(i + j) } //func06(7)(3)
(參數)=>{方法體--把邏輯代碼傳進來了}
參數只用一次可省略加個_便可(做爲佔位符)框架
def f1(f:(String) => Unit): Unit ={ f("kris") } def f2(s: String): Unit ={ println("Hello " + s) } //f1(f2) 這種方式還要聲明一個函數,太麻煩了,使用匿名函數 // 匿名函數 沒有名稱也不用聲明; 參數=>方法體;能夠傳一段邏輯代碼,擴展性強 遵循了OCP原則,擴展性開放 f1((x) => {println("Hello" + x)}) //Hello kris f1(println(_)) //kris 佔位符 //f1((x) => {println(x)}) 進一步的省略,這兩種寫法是等同的
柯里化練習| 把函數做爲參數傳進來jvm
Operator(1)(++)(1) -->> 2
def main(args: Array[String]): Unit = { def Operator(i: Int) = { //Operator應該返回一個函數;柯里化也就是閉包的概念,把一個完整的進行拆分一個個單一的函數;Operator並無執行,最終的執行結果應該靠++決定,但須要用它把它返回便可 def Middle(f:(Int, Int)=>Int) = { //第二個括號即Middle,效果是1+1的結果打印出來,應該把一個算法傳進來; def Inner(j: Int) ={ //第三個括號 f(i, j) //執行操做,傳進兩個參數 } Inner _ } Middle _ } def ++(i: Int, j: Int): Int = { //這裏執行具體功能,++ i / j //加減乘除均可以;邏輯能夠傳進來,但主題架構沒有變; } println(Operator(1)(++)(1)) //1 }
傳函數還要本身聲明---->再簡化,使用匿名函數
//val i: Int = Operator(1)((x, y) => (x + y))(10) val i: Int = Operator(1)(_+_)(10) //參數只用了一次還能夠省略 println(i) //11
//sum(3)(3)(println(_))-->> 6 def sum(i: Int) = { def middle(j: Int)={ def inner(f: (Int, Int) => Unit) = { f(i, j) } inner _ } middle _ } sum(3)(3)((x: Int, y: Int)=>{println(x+y)}) //6 //sum(3)(3)((x, y) => (println(x + y)))
遞歸有可能棧溢出;(不是內存溢出),棧內存溢出---跟線程有關;壓棧; 棧幀(即每塊內存)
// 遞歸 // 1) 函數本身調用本身 // 2) 函數中必定要有跳出的邏輯 // 3) 函數本身調用本身時傳遞的參數應該有規律 // 4)scala中遞歸函數須要明確返回值類型 def test(num: Int): Int ={ if (num == 1){ 1 }else{ num*test(num-1) } } println(test(5)) //120
JIT及時編譯器,可打亂程序執行順序
內存加載順序是由jvm決定,怎麼快怎麼來
windows、linux是線程優先級,蘋果系統是分時
看一個應用場景
惰性計算(儘量延遲表達式求值)是許多函數式編程語言的特性。惰性集合在須要時提供其元素,無需預先計算它們,這帶來了一些好處。
首先,您能夠將耗時的計算推遲到絕對須要的時候。其次,您能夠創造無限個集合,只要它們繼續收到請求,就會繼續提供元素。
函數的惰性使用讓您可以獲得更高效的代碼。Java 並無爲惰性提供原生支持,Scala提供了。
Java實現懶加載的代碼,好比經常使用的單例模式懶漢式實現時就使用了上面相似的思路實現
當函數返回值被聲明爲lazy時,函數的執行將被推遲,直到咱們首次對此取值,該函數纔會執行。這種函數咱們稱之爲惰性函數,在Java的某些框架代碼中稱之爲懶加載(延遲加載)。 def main(args: Array[String]): Unit = { lazy val res = sum(10, 20) println("########################") println("res=" + res) } def sum(n1: Int, n2: Int): Int ={ println("sum() 執行了..") return n1 + n2 } /* 不加lazy sum() 執行了.. ######################## res=30 加lazy ######################## sum() 執行了.. res=30 */
注意事項和細節
1)lazy 不能修飾 var 類型的變量
2)不可是 在調用函數時,加了 lazy ,會致使函數的執行被推遲,咱們在聲明一個變量時,若是給聲明瞭 lazy ,那麼變量值得分配也會推遲。 好比 lazy val i = 10
函數沒返回值就是過程--就是一段邏輯
map函數的做用是將集合中全部的數據進行邏輯操做,數據總量不變。map不支持偏函數,collect支持;
偏函數會致使不符合條件的數據被過濾掉,因此數據總量發生變化;過濾filter、groupBy
① 在對符合某個條件,而不是全部狀況進行邏輯操做時,使用偏函數是一個不錯的選擇
② 將包在大括號內的一組case語句封裝爲函數,咱們稱之爲偏函數,它只對會做用於指定類型的參數或指定範圍值的參數實施計算,超出範圍的值會忽略.
③ 偏函數在Scala中是一個特質PartialFunction
偏函數簡化形式
聲明偏函數,須要重寫特質中的方法,有的時候會略顯麻煩,而Scala其實提供了簡單的方法
def f2: PartialFunction[Any, Int] = { case i: Int => i + 1 // case語句能夠自動轉換爲偏函數 } val list: List[Any] = List(1, 2, 3, 4, 5, "abc").collect(f2) println(list)
進一步的簡化:
val list2 = List(1, 2, 3, 4, 5, "abc").collect { //偏函數,只對其中的一部分起做用 case i: Int => i + 2 //List(3, 4, 5, 6, 7) //再進一步的簡化:不用聲明函數直接寫個case便可 } println(list2) //List(3, 4, 5, 6, 7
扁平化栗子:
val list = List(1,2,List(3,4), "abc") println(list.flatMap(x => List(x))) //List(1, 2, List(3, 4), abc) 沒有真正的扁平化,1,2不能迭代; 區別對待,偏函數 println(list.flatMap{ // //case x: Int => List(x) case y: Iterable[Int] => y //List(1, 2, 3, 4, abc),case順序若是調換下就是List(1, 2, List(3, 4), abc)這個結果了 case a => List(a) })
再好比:
val map = Map("a"->1, "b"->2) map.foreach{ case(k, v) => { println(k + "=>" + v) } }
===>
a => 1
b => 2
將一段代碼(從形式上看),做爲參數傳遞給高階函數,在高階函數內部執行這段代碼. 其使用的形式如 breakable{} 。
知足以下條件:
① 參數是函數
② 函數參數沒有輸入值也沒有返回值
能夠將一段代碼邏輯做爲參數傳遞給函數(方法),有利於功能的擴展
Breaks.breakable { val list = List(1, 2, 3, 4) for (i <- list) { if (i == 3) { Breaks.break() } } }
模仿源碼:
class Breaks() extends scala.AnyRef { def breakable(op : => scala.Unit) : scala.Unit = { /* compiled code */ }
把一段邏輯寫進來就能夠寫{ } 花括號了;
test{ val list = List(1, 2, 3, 4) for (i <- list) { println(i) } } def test(f: => Unit)={ f //f()函數能夠調用,scala中要求f加括號了才能加,沒有加()也不能加 }
for (){
} //for若是不是關鍵字, for()()柯里化,把一段邏輯寫進來就能夠寫{ } 花括號了;
Scala提供try和catch塊來處理異常。try塊用於包含可能出錯的代碼。catch塊用於處理try塊中發生的異常。能夠根據須要在程序中有任意數量的try...catch塊。
語法處理上和Java相似,可是又不盡相同
Scala異常處理
ctrl+alt+t
/* Java異常處理回顧 try { // 可疑代碼 val i = 0 val b = 10 val c = b / i // 執行代碼時,會拋出ArithmeticException異常 } catch { case e: Exception => e.printStackTrace() } finally { // 最終要執行的代碼 System.out.println("java finally") }*/
try { val r = 10 / 0 } catch { case ex: ArithmeticException => println("捕獲了除數爲零的算術異常") case ex: Exception => println("捕獲了異常") } finally { // 最終要執行的代碼 println("scala finally") }
1)咱們將可疑代碼封裝在try塊中。 在try塊以後使用了一個catch處理程序來捕獲異常。若是發生任何異常,catch處理程序將處理它,程序將不會異常終止。
2)Scala的異常的工做機制和Java同樣,可是Scala沒有「checked(編譯期或受檢)」異常,即Scala沒有編譯異常這個概念,異常都是在運行的時候捕獲處理。
3)用throw關鍵字,拋出一個異常對象。全部異常都是Throwable的子類型。throw表達式是有類型的,就是Nothing,由於Nothing是全部類型的子類型,因此throw表達式能夠用在須要類型的地方
4)在Scala裏,借用了模式匹配的思想來作異常的匹配,所以,在catch的代碼裏,是一系列case子句來匹配異常。當匹配上後 => 有多條語句能夠換行寫,相似 java 的 switch case x: 代碼塊..
5)異常捕捉的機制與其餘語言中同樣,若是有異常發生,catch子句是按次序捕捉的。所以,在catch子句中,越具體的異常越要靠前,越廣泛的異常越靠後,若是把越廣泛的異常寫在前,把具體的異常寫在後,在scala中也不會報錯,但這樣是很是很差的編程風格。
6)finally子句用於執行不論是正常處理仍是有異常發生時都須要執行的步驟,通常用於對象的清理工做,這點和Java同樣。
7)Scala提供了throws關鍵字來聲明異常。可使用方法定義聲明異常。 它向調用者函數提供了此方法可能引起此異常的信息。 它有助於調用函數處理並將該代碼包含在try-catch塊中,以免程序異常終止。在scala中,可使用throws註釋來聲明異常