Scala的高級特性
高階函數
概念
Scala混合了面向對象和函數式的特性,我們通常將可以作為參數傳遞到方法中的表達式叫做函數。在函數式編程語言中,函數是“頭等公民”,高階函數包含:作為值的函數、匿名函數、閉包、柯裏化等等。
作為值的函數
可以像任何其他數據類型一樣被傳遞和操作的函數,每當你想要給算法傳入具體動作時這個特性就會變得非常有用。
定義函數時格式:val 變量名 = (輸入參數類型和個數) => 函數實現和返回值類型
“=”表示將函數賦給一個變量
“=>”左面表示輸入參數名稱、類型和個數,右邊表示函數的實現和返回值類型
匿名函數
在Scala中,你不需要給每一個函數命名,沒有將函數賦給變量的函數叫做匿名函數。
由於Scala可以自動推斷出參數的類型,所有可以寫的跟精簡一些
還記得神奇的下劃線嗎?這才是終極方式
柯裏化
什麽是柯裏化
柯裏化(Currying)指的是把原來接受多個參數的函數變換成接受一個參數的函數過程,並且返回接受余下的參數且返回結果為一個新函數的技術。
例子
(1) 一個普通的非柯裏化的函數定義,實現一個加法函數:
scala> def plainOldSum(x:Int,y:Int)=x+y plainOldSum: (x: Int, y: Int)Int scala> plainOldSum(1,2) res0: Int = 3
(2) 使用“柯裏化”技術來定義這個加法函數,原來函數使用一個參數列表,“柯裏化”,把函數定義為多個參數列表:
scala> def curriedSum(x:Int)(y:Int)=x+y curriedSum: (x: Int)(y: Int)Int scala> curriedSum(1)(2) res1: Int = 3 當你調用curriedSum (1)(2)時,實際上是依次調用兩個普通函數(非柯裏化函數), 第一次調用使用一個參數x,返回一個函數類型的值, 第二次使用參數y調用這個函數類型的值。
(3) 使用下面兩個分開的定義在模擬curriedSum柯裏化函數:
首先定義第一個函數: scala> def first(x:Int)=(y:Int)=>x+y first: (x: Int)Int => Int 然後我們使用參數1調用這個函數來生成第二個函數: scala> val second =first(1) second: Int => Int = <function1> scala> second(2) res2: Int = 3
(4) 使用curriedSum 來定義second
scala> val onePlus=curriedSum(1)_ onePlus: Int => Int = <function1> 下劃線“_” 作為第二參數列表的占位符, 這個定義的返回值為一個函數,當調用時會給調用的參數加一。 scala> onePlus(2) res3: Int = 3 調用生成的函數,給函數傳入參數,即可得到我們想要的結果。
總結
scala柯裏化風格的使用可以簡化主函數的復雜度,提高主函數的自閉性,提高功能上的可擴張性、靈活性。可以編寫出更加抽象,功能化和高效的函數式代碼。
閉包
什麽是閉包
閉包是一個函數,返回值依賴於聲明在函數外部的一個或多個變量。
閉包通常來講可以簡單的認為是可以訪問不在當前作用域範圍內的一個函數。
例子
package cn.itcast.closure /** * scala中的閉包 * 閉包是一個函數,返回值依賴於聲明在函數外部的一個或多個變量。 */ object ClosureDemo { def main(args: Array[String]): Unit = { val y=10 //變量y不處於其有效作用域時,函數還能夠對變量進行訪問 val add=(x:Int)=>{ x+y } //在add中有兩個變量:x和y。其中的一個x是函數的形式參數, //在add方法被調用時,x被賦予一個新的值。 // 然而,y不是形式參數,而是自由變量 println(add(5)) // 結果15 } }
隱式轉換和隱式參數
隱式轉換
Scala提供的隱式轉換和隱式參數功能,是非常有特色的功能。是Java等編程語言所沒有的功能。它可以允許你手動指定,將某種類型的對象轉換成其他類型的對象或者是給一個類增加方法。通過這些功能,可以實現非常強大、特殊的功能。
Scala的隱式轉換,其實最核心的就是定義隱式轉換方法,即implicit conversion function。定義的隱式轉換方法,只要在編寫的程序內引入,就會被Scala自動使用。Scala會根據隱式轉換方法的簽名,在程序中使用到隱式轉換方法接收的參數類型定義的對象時,會自動將其傳入隱式轉換方法,轉換為另外一種類型的對象並返回。這就是“隱式轉換”。其中所有的隱式值和隱式方法必須放到object中。
然而使用Scala的隱式轉換是有一定的限制的,總結如下:
- implicit關鍵字只能用來修飾方法、變量(參數)。
- 隱式轉換的方法在當前範圍內才有效。如果隱式轉換不在當前範圍內定義(比如定義在另一個類中或包含在某個對象中),那麽必須通過import語句將其導。
隱式參數
所謂的隱式參數,指的是在函數或者方法中,定義一個用implicit修飾的參數,此時Scala會嘗試找到一個指定類型的,用implicit修飾的參數,即隱式值,並註入參數。
Scala會在兩個範圍內查找:
- 當前作用域內可見的val或var定義的隱式變量;
- 一種是隱式參數類型的伴生對象內的隱式值;
隱式轉換方法作用域與導入
(1)Scala默認會使用兩種隱式轉換,一種是源類型或者目標類型的伴生對象內的隱式轉換方法;一種是當前程序作用域內的可以用唯一標識符表示的隱式轉換方法。
(2)如果隱式轉換方法不在上述兩種情況下的話,那麽就必須手動使用import語法引入某個包下的隱式轉換方法,比如import test._。通常建議,僅僅在需要進行隱式轉換的地方,用import導入隱式轉換方法,這樣可以縮小隱式轉換方法的作用域,避免不需要的隱式轉換。
隱式轉換的時機
(1)當對象調用類中不存在的方法或成員時,編譯器會自動將對象進行隱式轉換
(2)當方法中的參數的類型與目標類型不一致時
隱式轉換和隱式參數案例
隱式轉換案例一
(讓File類具備RichFile類中的read方法)
package cn.itcast.implic_demo import java.io.File import scala.io.Source object MyPredef{ //定義隱式轉換方法 implicit def file2RichFile(file: File)=new RichFile(file) } class RichFile(val f:File) { def read()=Source.fromFile(f).mkString } object RichFile{ def main(args: Array[String]) { val f=new File("E://words.txt") //使用import導入隱式轉換方法 import MyPredef._ //通過隱式轉換,讓File類具備了RichFile類中的方法 val content=f.read() println(content) } }
隱式轉換案例二
(超人變身)
package cn.itcast.implic_demo class Man(val name:String) class SuperMan(val name: String) { def heat=print("超人打怪獸") } object SuperMan{ //隱式轉換方法 implicit def man2SuperMan(man:Man)=new SuperMan(man.name) def main(args: Array[String]) { val hero=new Man("hero") //Man具備了SuperMan的方法 hero.heat } }
隱式轉換案例三
(一個類隱式轉換成具有相同方法的多個類)
package cn.itcast.implic_demo class A(c:C) { def readBook(): Unit ={ println("A說:好書好書...") } } class B(c:C){ def readBook(): Unit ={ println("B說:看不懂...") } def writeBook(): Unit ={ println("B說:不會寫...") } } class C object AB{ //創建一個類的2個類的隱式轉換 implicit def C2A(c:C)=new A(c) implicit def C2B(c:C)=new B(c) } object B{ def main(args: Array[String]) { //導包 //1. import AB._ 會將AB類下的所有隱式轉換導進來 //2. import AB._C2A 只導入C類到A類的的隱式轉換方法 //3. import AB._C2B 只導入C類到B類的的隱式轉換方法 import AB._ val c=new C //由於A類與B類中都有readBook(),只能導入其中一個,否則調用共同方法時代碼報錯 //c.readBook() //C類可以執行B類中的writeBook() c.writeBook() } }
隱式參數案例四
(員工領取薪水)
package cn.itcast.implic_demo object Company{ //在object中定義隱式值 註意:同一類型的隱式值只允許出現一次,否則會報錯 implicit val aaa="zhangsan" implicit val bbb=10000.00 } class Boss { //註意參數匹配的類型 它需要的是String類型的隱式值 def callName()(implicit name:String):String={ name+" is coming !" } //定義一個用implicit修飾的參數 //註意參數匹配的類型 它需要的是Double類型的隱式值 def getMoney()(implicit money:Double):String={ " 當月薪水:"+money } } object Boss extends App{ //使用import導入定義好的隱式值,註意:必須先加載否則會報錯 import Company._ val boss =new Boss println(boss.callName()+boss.getMoney()) }
Scala的高級特性