[Kotlin Tutorials 6] Lambda表達式和高階函數

Lambda表達式和高階函數

在Kotlin中函數是第一公民, 不像Java同樣, 函數必定須要寫在某個類裏面, Kotlin中的函數也能夠直接寫在文件裏.html

Lambda表達式並非什麼新東西, Java 8就有了. 它的存在主要是爲了讓代碼更加簡潔.bash

高階函數呢, 基本概念也很簡單, 就是函數, 一樣也能夠像其餘類型的對象同樣, 做爲一個函數的參數和返回值.ide

Lambda表達式

lambda表達式和匿名函數都是函數字面值(function literals), 它們沒有提早聲明, 直接做爲表達式傳入.函數

Lambda表達式的語法

lambda表達式的構成:ui

{ 參數列表 -> 函數體 }
複製代碼

其中參數類型是能夠省略的. 若是函數體的返回值不是Unit, 那麼最後一個表達式會被當作返回值.spa

舉例: setOnClickListener

對Lambda表達式的應用很大程度上要感謝現代化的IDE, 好比一個簡單的給button設置listener的代碼,code

最開始, 做爲一個把Kotlin當Java 7用的人, 可能會寫成這樣:htm

button.setOnClickListener(object : View.OnClickListener{
    override fun onClick(v: View?) {
    }
})
複製代碼

這裏利用了object關鍵字, 聲明瞭一個匿名類的對象.對象

可是IDE看到這個代碼就不那麼開心了, 出現了一條黃色的下劃線, Alt + Enter, "Convert to lambda", 再Enter確認, 就變成了這樣:接口

button.setOnClickListener { }
複製代碼

爲何能夠這樣幹呢, 這是由於ViewOnClickListener接口是一種特殊類型的接口: 只有一個方法.

這種只擁有一個方法的接口被稱做是函數式接口(functional interface), 也被叫作單抽象方法類型, 即SAM, (single abstract method).

PS: 一個錯誤的示範: 若是你不幸一開始把代碼寫成了這樣:

button.setOnClickListener{object : View.OnClickListener {
    override fun onClick(v: View?) {
    }
}}
複製代碼

注意這裏與上面例子的區別僅僅是把小括號()變成了大括號{}. 程序沒有報錯, 但按鈕點擊的時候應該是執行不到你想要的代碼了.

IDE仍然會提示你簡化, 簡化後變成了這樣:

button.setOnClickListener{ View.OnClickListener { } }
複製代碼

也就是說每次按鈕點擊, 你作的事情都是建立了一個匿名對象.

爲何會犯這種錯誤, 而IDE又不提示呢, 這是由於lambda的慣例容許這樣的寫法, 這麼寫雖然邏輯上有問題, 可是語法上是沒有錯誤的. 下面請看都有什麼慣例呢.

lambda慣例

  • 若是函數的最後一個參數接收函數, 傳入的lambda表達式能夠寫在圓括號外面. (這種語法稱爲trailing lambda). 在這種狀況下, 若是lambda是惟一的參數, 那麼圓括號能夠省略. (上面的錯誤例子就是由於符合了這個慣例, 因此語法上沒有錯誤).
  • 若是lambda只有一個參數, 能夠不聲明直接用, 隱式名字it.
  • lambda表達式的返回值: 能夠顯式return, 若是不顯式返回, 默認返回最後一個表達式的值.
  • 沒有用到的參數能夠用下劃線_表示.

匿名函數

lambda表達式並不能顯式指定返回類型, 大多數狀況下可能用不上這一點, 由於每每返回類型都是能夠被自動推斷出來的.

可是若是你真的須要顯式指定, 你能夠用匿名函數.

fun(x: Int, y: Int): Int = x + y
複製代碼

匿名函數看起來和普通的函數聲明很像, 只是它沒有名字.

和普通函數不一樣的是, 若是參數類型能夠從上下文推斷出來, 那麼能夠省略不寫參數類型:

ints.filter(fun(item) = item > 0)
複製代碼

匿名函數的返回類型推斷和正常函數同樣.

匿名函數與Lambda的區別:

  • 匿名函數的參數永遠是在圓括號內傳遞的, 把函數參數放在圓括號外的簡化語法只適用於lambda表達式.
  • 不帶標籤的return語句: 在lambda中, 返回外層的函數, 在匿名函數中, 返回本函數.

高階函數

高階函數(Higher-Order Functions): 把函數做爲參數或返回值的函數.

函數類型

函數類型(Function types): 函數根據參數和返回值, 能夠歸類到一個函數類型.

函數類型的寫法

函數類型的基本寫法: 括號中的參數列表和一個返回值. 好比(A, B) -> C就是一個函數類型, 這種類型的函數接受A和B兩種類型的參數, 返回一個類型C的返回值.

  • 參數列表能夠爲空, 好比() -> A.
  • 返回值爲Unit時不能省略.
  • 函數類型還能夠寫接收器(receiver)類型: A.(B) -> C. 表示這種函數是在A類型上調用的.
  • suspend關鍵字表示一種特殊的函數類型, 因此若是有, suspend修飾符也要出現. 好比suspend () -> Unit.
  • 也能夠加上參數名, 來表達參數的意義. 如(x: Int, y: Int) -> Point.

幾點比較特殊的:

  • 函數類型也有可空類型, 加括號和?. 好比((Int, Int) -> Int)?.
  • 函數類型能夠是高階的, 用括號組合, 如: (Int) -> ((Int) -> Unit).
  • 箭頭是按照右結合的原則, 即運算按照從右向左的順序, 因此(Int) -> (Int) -> Unit(Int) -> ((Int) -> Unit)是一個意思.

能夠給函數類型一個類型別名, 好比:

typealias ClickHandler = (Button, ClickEvent) -> Unit
複製代碼

實例化函數類型

實例化函數類型有好幾種方法:

  • 用函數字面值(function literal)的代碼塊: lambda表達式, 匿名函數.
  • 使用已有的聲明引用: 函數, 屬性, 構造函數. ::的用法參見Callable Reference.
  • 函數類型還能夠當作接口被類實現, 那麼建立這個類的實例就是建立了函數類型的實例.

調用函數類型的實例

函數類型聲明瞭, 也實例化了, 怎麼調用呢? 能夠用invoke操做符, 也能夠直接用名稱調用.

若是有接受者類型(receiver), 那麼接受者對象須要做爲第一個參數. 另外一種方式也能夠將接受者對象放在點(.)前面, 像擴展函數同樣調用.

val stringPlus: (String, String) -> String = String::plus
val intPlus: Int.(Int) -> Int = Int::plus

println(stringPlus.invoke("<-", "->"))
println(stringPlus("Hello, ", "world!")) 

println(intPlus.invoke(1, 1))
println(intPlus(1, 2))
println(2.intPlus(3)) // extension-like call
複製代碼

參考

相關文章
相關標籤/搜索