寫在前面
眾所周知,scala一向宣稱自己是面向函數的編程,(java表示不服,我是面向bean的編程!)那什么是函數?
在接觸java的時候,有時候用函數來稱呼某個method(實在找不出詞了),有時候用方法來稱呼某個method,雖然method的中文翻譯就是“方法”,但對於java來說,方法和函數是等價的,或者說沒有函數這個概念。
而對於scala,這兩者似乎有一個較為明確的邊界。
你會發現滿世界的函數,而你卻在寫方法
Scala 方法&函數
方法
Scala的方法和java可以看成是一樣的,只是多了點語法糖。
比如無參方法在申明時可以不加括號,甚至在調用過程也不用加括號
def f = 1+1 println(f)
比如方法可以添加泛型規則,這在java中只能在類申明
def f[T](t: T) = {t}
還有其它很多細節語法,遇到才深入吧
一般而言只要知道函數的結構就行(但是我想說,spark的代碼就沒有一個函數長成這樣的啊..),請忽略下圖的“函數”字樣,其實就是方法
方法應用
def method(): Unit ={ //本地方法 def print(str:String): Unit ={ println(str) } print("hello") }
方法的語法還是跟java差不多的,只是有些可以省略而已。
比較重要的就是本地方法,即方法中嵌套方法
函數
Scala的函數是基於Function家族,0-22,一共23個Function Trait可以被使用,數字代表了Funtcion的入參個數
函數語法
下面這四個函數的意義是一樣的
// println(fun1) // println(fun2) // println(fun3) // println(fun4) // 都為<function2> val fun1 = new Function2[Int,Int,Int]() { override def apply(v1: Int, v2: Int): Int = { v1+v2 } } val fun2 = new ((Int, Int) => Int)() { override def apply(v1: Int, v2: Int): Int = { v1+v2 } } val fun3 = (v1:Int,v2:Int) => v1+v2 // _可以把method轉換成function val fun4 = fun4Method _ def fun4Method(v1:Int,v2:Int): Int = { v1+v2 }
一般我們都采用第三種fun3定義方式,也是最難懂的一個定義方式。具體結構參考下圖
那函數有什么用呢?
Java里只有方法都能適應一切需求,那scala又提出函數的概念肯定有意義。
1.函數可以直接賦值給變量,可以讓函數很方便的傳遞
2.閉包(closure),可以把靈活操作代碼塊,從而引申出其他靈活的語法
函數應用
在spark中,有很多方法入參中使用函數的場景,比如如下函數
defrunJob[T,U](fun: Iterator[T] => U ,resHandler: (Int, U) => Unit): Unit ={ //忽略里面的邏輯 }
其中的fun和resHandler都是函數
Fun是入參為Iterator[T],返回值為U的函數,一個入參的函數其實就是Function1的實例
resHandler是入參為Int和 U無返回值的函數,二個入參的函數其實就是Function2
模擬spark中常見的一段代碼語法,拿一個普通scala類型的例子來說
//模擬spark的runJob方法 def runJob[T,U](fun: Iterator[T] => U ,resHandler: (Int, U) => Unit): Unit ={ val listBuffer = new ListBuffer[T] listBuffer.append("h".asInstanceOf[T]) listBuffer.append("e".asInstanceOf[T]) listBuffer.append("l".asInstanceOf[T]) listBuffer.append("l".asInstanceOf[T]) listBuffer.append("o".asInstanceOf[T]) //這里調用函數其實用到了伴生對象的概念,fun(xxx)就是fun.apply(xxx) val res = fun(listBuffer.iterator) //spark中,這里是每個partition的數據都存入arr,這里做模擬就一個partition了:) resHandler(0,res) } //模擬調用runJob的方法 def main(args: Array[String]): Unit = { val arr = new Array[String](1) //fun函數的實際邏輯 val fun = (it:Iterator[String]) => { val sb = new StringBuilder() while (it.hasNext) sb.append(it.next()) sb.toString() } //resHandler函數的實際邏輯 val resHandler = (i:Int,res:String) => arr(i) = res runJob[String,String](fun ,resHandler) println(arr.mkString("")) }
其實就是傳遞函數的邏輯,和java的匿名類差不多(只有一個方法的匿名類),只是多了點語法糖
這么做的好處也是不言而喻的
1.可以構造出更抽象的方法,使得代碼結構更簡潔
2.spark的思想就是lazy,而函數傳遞也是一個lazy的過程,只有在實際觸發才會執行
偏函數
英文為PartialFunction,不知道這么翻譯對不對,貌似都這么叫。
PartialFunction其實是Funtion1的子類
參考源碼
trait PartialFunction[-A, +B] extends (A => B)
A => B就是標准的函數結構
那PartialFunction有什么作用呢?
模式匹配!
PartialFunction最重要的兩個方法,一個是實際的操作邏輯,一個是校驗,其實就是用來做模式匹配的。
參考資料
《Scala編程》