開始前先複習一下 Higher-Order function ,它是 first-class function,因此可以用在當作其他 function 的參數、函數返回值、當作變數使用以及支持 lambda,昨天已經簡單帶到了結構與概念,那今天要更深入的探討
Kotlin 中如果要使用 Funciton 當作 Method 的參數,那麼就需要使用到 () -> T
這類型的 Function type 去告訴 Compiler
Example
// Void function,沒有輸入值,沒有輸出值
() -> Unit
// 輸入值 Int, 輸出值 String
(Int) -> String
// 輸入值是兩個 Long,輸出值是 Location
(Long, Long) -> Location
// 輸入值是一個 Location 和 一個 function,輸出值是 Int
(Location, ((Long, Long) -> Location)) -> Int
// 輸入值是一個 Int,輸出值是另一個 function
(Int) -> ((Int)-> Unit)
假設今天要把使用者目前位置的經緯度重新 Format 後,再顯示給使用者,那麼我們就會需要使用者目前位置的經度( Double )與緯度( Double ),並回傳 Format 後的 String 給使用者,因此編寫起來就會長這樣,這樣各位讀者能了解嗎?
Example:
fun main() {
println(coordinateFormate(23.55, 121.05))
}
val coordinateFormate: (Double, Double) -> String = { lat, lon ->
"Coordinate: ($lat, $lon)"
}
那在 Function type 使用上需要記住幾個規則:
(A, B) -> C
,而括號中的參數類型也可能是空的 () -> C
,如果連返回值都不要,可以透過 () -> Unit
達到A.(B) -> C
suspend () -> Unit
typealias MyHandler = (Int, String, Any) -> Unit
在寫 lambda 表示式和匿名函式( Anonymous function )時,會發現其實他們可以用來取代 Function types 表示法
以剛剛上面的例子延伸的話,那用 lambda 及匿名函式改寫會長的像這樣
Example:
// Function types 版本
val coordinateFormate: (Double, Double) -> String = { lat, lon ->
"Coordinate: ($lat, $lon)"
}
// Lambda expression 版本
val coordinateFormate: String = { lat: Double, lon: Double ->
"Coordinate: ($lat, $lon)"
}
// Anonymous function 版本
fun(lat: Double, lon: Double): String {
return "Coordinate: ($lat, $lon)"
}
上面有簡單的示範了 Lambda expression 大概的長相,在 Kotlin 中,只要用大括號包起來的部份都可以算是 Function ,在括號中分成 parameters
和 body
, body 是寫邏輯和處理 parameters 的區塊
Lambda 在被建立出來的時候並不會 new 一個新的實體出來,而是會把區塊內的 code 先存到 Memory 中,當其他 function 需要的時候在透過 Call Function
的方式呼叫,這樣的寫法下 Function 可以當作變數來呼叫,因此彈性很大,所以很常用來當作另一個 Function 的傳入值
像是在寫 OnClickListener 時,就可以使用 Lambda 簡化程式碼,這邊給一個用正統方式和 Lambda 方式的比較範例,兩種攤開來比較就更會對 Lambda 更有感覺
Example:
// 正統版本
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
Log.d("Original", "Hello")
}
})
// Lambda 版本
view.setOnClickListener({ v -> Log.d("Lambda", "Hello") })
另外,如果你的 Lambda function 是 Function 傳入值的最後一個參數,那麼可以把他寫在 Function 外面,這樣講會有點難懂,所以直接給個範例看看
fun showCircleProgressBar(message: String, asyncTask: () -> Unit) {
val progressBarHolder: View = MainActivity.progressBarHolder
GlobalScope.launch(Dispatchers.Main) {
Log.d("showCircleProgressBar", message)
progressBarHolder.visibility = View.VISIBLE
withContext(Dispatchers.IO) { asyncTask() }
progressBarHolder.visibility = View.GONE
}
}
fun main() {
showCircleProgressBar("Start doing async task"){
// 做讀檔或是一些需要長時間的工作
readFile()
storeToDataBase()
}
}
showCircleProgressBar
的 Function, Function 的傳入值是另一個 function asyncTask
asyncTask
是一個 Void FunctionshowCircleProgressBar
中,寫的一個 GlobalScope
去處理非同步工作,並且告訴他要在 IO thread 上處理非同步的工作showCircleProgressBar()
showCircleProgressBar("Start doing async task", {
readFile()
storeToDataBase()
})
但是 Lambda function 是傳入的最後一個參數,因此大括號可以擺到小括號外面,因此會長成上面範例的模樣,那大家可能會想說位啥要這樣寫呢?
根據我讀的文章的解釋,有一派人覺得這樣寫比較符合 FP 的模樣,在可讀性上會比較高,可是有些人對這種寫法還是無法接受,但如果你是用 Android Studio 寫上面那段 code 的話, JetBrains 也會建議你把大括號擺出去,因此以我的觀點還是會想要去符合 JetBrains 推荐的方法,會比較符合生態的習慣
Inline function 是 Java 中並沒有的概念,他的特色是當你調用一個 Inline function 時,Compiler 不會產生 Function call ,而是將 Inline function 中的 code 嵌入到調用的地方
那把 Function 轉成 inline 的方法很簡單,只要在原本的 Function 前面加上 inline
即可
例如在寫 ViewModel 的時候,原本寫法會是這樣,這樣的壞處是如果 ViewModel 要被使用到很多次,那麼舊會出現一堆這種東西
Example:
val mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
因此可以採用 inline 來解決這個問題,我們可以先把 ViewModel 的部份抽出來變成 Extension function,然後在這個 Function 前面加上 inline 即可
Example:
inline fun <reified T: ViewModel> AppCompatActivity.getViewModel(): T {
return ViewModelProviders.of(this).get(T::class.java)
}
當我們在 Activity 需要使用 viewmodel
時,只需要這樣寫,就可以達到上面範例的效果,是不是看起來清爽很多阿~
Example:
val mainViewModel = getViewModel<MainViewModel>()
那 inline 的好處就是解決每次使用 Higher-Order function 時都必須產生 Object 與 Closure 的 Memory 消耗和運行時間開銷
但反過來說,使用 Inline function 時,會在生成一份一樣的 code 嵌入掉用的地方,因此如果是很大的 Function 使用 Inline 的話,那麼執行性能上還是會被影響的
我對 Higher-Order function 的使用上也還沒有很熟悉,因此只能依照我目前了解的部份下去寫,這次介紹也讓我重新在讀了相關的資料,感覺有更深入了解一點。那 Higher-Order function 的部份應該就先講到這樣,如果在觀念上有錯誤或是不足的部份,歡迎各位大大留言指教~