情境
我們來討論一下 Kotlin 的 Functions,一般來說,函式在 Kotin 裡面是屬於 First-class object (第一類物件),什麼是第一類物件? 簡單來說,就是函式可以傳進函式當作參數,也可以回傳函式當作參數值,這類的程式語言特性就稱之為 Functional Programming。
說明
基本上 Function 分成三大類。
-
一般函式(Introduction Functions)
-
高階函式(Higher-order Functions and Lambdas)
-
行內函式(Inline Functions)
一般的函式有哪些?
Member Function
很一般的函式,通常可以選擇傳或不傳入一個參數,並且可以設計有或沒有回傳值,如果有回傳值,則最後會用 retrun
的方式回傳對應的值。
class Sample() {
fun foo() { print("Foo") }
}
Tail Recursive Functions
遞迴函式是一種很常見型態,它會在內部再次的呼叫自己,直到條件滿足為止。
fun recursiveFactorial(n: Long) : Long {
return if (n <= 1) {
n
} else {
n * recursiveFactorial(n - 1)
}
}
Generic Functions
泛型對於方法來說是一種很方便的處理方式,如果沒有泛型,不同型態之間就必須寫入多個方法來實現。
fun <T> singletonList(item: T): List<T> {
/*...*/
}
Extension Functions
這邊的延伸方法,就是在既有的資料結構下,新增該類別的一個新方法。
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
Higher-order Functions
只要傳入函式或者回傳函式或者兩者皆是,則代表它是高階函式。
fun runTransformation(f: (String, Int) -> String): String {
return f("hello", 3)
}
Inline Functions
行內函式是 Kotlin 的一種特殊存在,它可以讓執行效率變好,後續會再談。
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
而這當中我們要著墨的重點在於高階函式,什麼是高階函式呢?
任何以 Lambda 或者函式引用作為參數的函式,或者返回值為 Lambda 或函式引用,或者兩者都滿足的皆稱為高階函式。
我們來說明一下什麼是 Lambda?
Lambda 就是可以傳遞給其他函式的一段程式碼。
通常我們都會這樣來進行表示,比較像是 Java 中的匿名函式(anonymous function)。
view.setOnClickListener{
//do some thing
}
Collection API
在 Kotlin 中,在集合類別提供了許多 API 可以讓你輕鬆操作集合,以下就舉幾個常見的例子來做說明。
- foreach
- filter
- map
- reduce
- flatmap
foreach
foreach 顧名思義就是每一個都跑過一次,所以下面程式可以看到輸出列表中所有的人名,其中,Lambda 中是可以拿來判斷要輸出什麼資訊,這個通常都會被拿來當作 for 迴圈使用。
val people = listOf(Person("Alice", 29), Person("Bob", 31))
people.foreach{
print(it.name) // output : Alice Bob
}
可以看到下圖,從左邊到右邊,每個元素都逐一盤點。

filter
從字面上的意思就可以知道叫做過濾器,因此,我們在 Lambda 中就可以設定條件來選出要的物件。
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val pList = people.filter{
it.age > 30
}
print(pList) // output: [Person(name=Bob, age=31)]
從下圖得知,如果要過濾正方形,就將條件設定為正方形通過,那麼出來的結果就會是正方形的列表了。

map
map 可以解釋成對應或者轉化,把某一個列表轉化成你想要變成的形狀。
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val pList = people.map{
it.name
}
print(pList) // output: [Alice, Bob]
從下圖可以清楚看出我們原本列表有的東西,將其整理為新的列表,或者將原本列表沒有的東西轉化成相同的東西。

reduce
reduce 通常會搭配一個 map 來進行操作,它的意思很像把 map 轉換完畢的列表歸納 (reduce) 成一個結果。
val people = listOf(Person("Alice", 29), Person("Bob", 31))
val pList = people.map{
it.name
}.reduce{ acc, s->
"$acc,$s"
}
print(pList) // output: Alice,Bob
從下圖我們可以看到,當 map 結束以後,我們將所有的人名都加入一個逗號 (,) 來進行區隔。

flatmap
flatmap 這個操作比較難以理解,其實你只需要想像它把一個二維陣列攤平變成一維陣列,就大概能夠了解它的意義了。
val people = listOf(
Person("Alice", 29, arrayListOf("A", "B")),
Person("Bob", 31, arrayListOf("C", "D")))
val books = people.flatmap{
it.bookList
}
print(books) // output: [A, B, C, D]
從下圖就可以很容易看出他的觀念。

Eager evaluation vs Lazy Evaluation
-
及早求值 (Eager evaluation):代表著每次執行 filter 或者 map 等高階函式,會立刻回傳一個列表。
-
惰性求值 (Lazy Evaluation):透過 asSequence 這個方法,讓最末端的操作才將值輸出。
在下面的程式碼,中間 map 以及 filter 都會立刻回傳一個列表,再透過這個列表進行下一次的運算,這樣一來,我們如果進行多個操作,則會在記憶體內產生許多的列表。
people.map(People::name).fliter{ it.startWith("A")}
透過以下程式碼的改良,我們就可以在最後末端操作才輸出列表,末端 toList 稱之為終端操作。
people.asSequence()
.map(Person::name)
.filter { it.startsWith("A") }
.toList()
Collection vs Sequences
這邊官方部落格有更詳細的解說,以下圖片是從官方部落格節錄出來的,它說明了惰性求值優於急性求值。

Switch Position
有時候我們在進行高階函式操作的時候,更換位置也可以改善一些效能問題。
people.map(People::name).filter{ it.name.startWith("A")}
在這個範例中,我們只需要將 map 以及 filter 的位置進行更換,馬上就可以將 map 的數量篩選掉一些。
people.filter{ it.name.startWith("A")}.map(People::name)
以上就是簡單的高階函式操作了。