--- title: 'Kotlin 函數編程 & 集合' disqus: kyleAlien --- Kotlin 函數編程 & 集合 === ## Overview of Content 以下參考,第一行代碼 (第三版), Kotlin 進階實戰 如有引用參考本文章請詳註出處,感謝 :smile: :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/) ::: [TOC] ## Lambda Kotlin 從第一版開始就支持 Lambda 編程,並且 Lambda 在 Kotlin 的功能極為強大,這裡會先以基礎為例,還有其他高階函數、DSL...等等 ### Lambda & Kotlin: Step by Setp * 這裡的重點是學習 **函數式 API 的++語法結構++,就是 Lambda 表達式在 Kotlin 中的運用,==Lambda 可以想做把函式當作參數傳入==** 先來看看 Lambda 表達式在 Kotlin 中的語法 ```kotlin= /** * 1. 最外面大括號 * 2. 參數列表的尾端使用 `->` 符號 * 3. 並且 Lambda 最後一行為返回的數值 */ {參數名1: 參數類型, 參數名2: 參數類型 -> 函數體} ``` 以下在集合中求取最大長度的字串,第一個是傳統的方式,第二個是 Lambda 的使用,之後皆是推導 (不然還真的看不懂) ```java= package class_3 val list = listOf<String>("Apple", "Banana", "Orange", "Pear", "Grape", "Watermelon") fun main() { customCompare() kotlinLambda() kotlinLambda1() kotlinLambda2() kotlinLambda3() kotlinLambda4() kotlinLambda5() } /** * 傳統比對方式 */ fun customCompare() { var maxLength = "" for (i in list) { if (i.length > maxLength.length) { maxLength = i } } println("custom Max Length is: $maxLength") } /** * Lambda...看沒有 ? 往下看 */ fun kotlinLambda() { // maxBy 函數原型 // public inline fun <T, R : Comparable<R>> Iterable<T>.maxBy(selector: (T) -> R): T? {...} val maxLength = list.maxBy { it.length } println("lambda Max Length is: $maxLength") } /** * 第一步 * 依照 Lambda 表達式先創建 */ fun kotlinLambda1() { val lambda = {i : String -> i.length} println("Lambda - 1, lambda: $lambda") // 1 val maxLength = list.maxBy(lambda) println("Lambda - 1: $maxLength") } /** * 第二步 * 不須定義一個 Lambda 變量 */ fun kotlinLambda2() { val maxLength = list.maxBy( {i: String -> i.length} ) // 2 println("Lambda - 2: $maxLength") } /** * 第三步 * 當最後一個參數為 Lambda 時可以將 Lambda 移到 () 外面 * * 第四步 * 若 Lambda 為唯一一個參數時可以移除 () */ fun kotlinLambda3() { // val maxLength = list.maxBy() { i: String -> i.length} // 3 val maxLength = list.maxBy { i: String -> i.length} // 4 println("Lambda - 3/4: $maxLength") } /** * 第五步 * 由於 Kotlin 有自動推倒機制,所以可以省略參數的 :String 類型 * (參數列表中大多數都不須宣告類型) */ fun kotlinLambda4() { val maxLength = list.maxBy { i -> i.length} // 5 println("Lambda - 5: $maxLength") } /** * 第六步 * 若 "只有一個參數" 則可以不用聲明參數,可以使用關鍵字 it */ fun kotlinLambda5() { val maxLength = list.maxBy { it.length} // 6 println("Lambda - 6: $maxLength") } ``` * 以下整理重點步驟 1. **Lambda 原型** ```kotlin= list.maxBy{(i: String -> i.length)} ``` 2. **當最後一個參數為 Lambda 時可以將 Lambda 移到 `()` 外面** ```kotlin= list.maxBy() {i: String -> i.length} ``` 3. **若 Lambda 為唯一一個參數時可以移除小括號 `()`** ```kotlin= list.maxBy{i: String -> i.length} ``` 4. **由於 Kotlin 有自動推倒機制,所以可以省略參數的類型** ```kotlin= list.maxBy{i -> i.length} ``` 5. **若「只有一個參數」則可以不用聲明參數,可以使用關鍵字 `it`** ```kotlin= list.maxBy{it.length} ``` **--實作結果--** > ![](https://i.imgur.com/da21w7b.png) :::success * 返回一個函數:一般來講程式語言只能返回基礎類型,但是函數式編程的特點就在於,可以將「函數作為值」返回 > `Lambda` 同時也可以作為一個返回值返回 ```kotlin= enum class ControlType { ADD, SUB } fun getLambda(type : ControlType) : (Int, Int) -> Int { return when(type) { ControlType.ADD -> { a, b -> a + b} ControlType.SUB -> { a, b -> a - b} } } fun main() { val lambda = getLambda(ControlType.ADD) println("Add res: ${lambda(1, 2)}") val lambda2 = getLambda(ControlType.SUB) println("Sub res: ${lambda2(10, 1)}") } ``` > ![](https://i.imgur.com/UJK7Vph.png) ::: :::info * Lambda 中「未」使用到的參數建議使用 `_` 取代其參數名稱,這可以增加程式的可讀性! ::: ### 解析 Kotlin Lambda:ByteCode * 現在換個角度來看看 Lambda 表達式:先撰寫 Kotlin 一個帶有 Lambda 函數式的程式,再將其編譯,最後查看 `.class` 文件 ```kotlin= fun main() { val sum = { x: Int, y : Int -> x + y } println(sum(1, 2)) } ``` :::success * 透過 IDE 提供的 `kotlin Bytecode` 工具查看該文件的 `.class` 檔案 > ![image](https://hackmd.io/_uploads/B1KSfNmap.png) ::: 1. **呼叫 `sum` Lambda 函數**:可以看到它調用了 Function2#invoke 方法(`Function2` 代表帶兩個參數的函數) > ![](https://i.imgur.com/Iep4KUu.png) 2. 而 `sum` 函數則實作了 **Function2** 介面 > ![](https://i.imgur.com/MUWU0In.png) :::danger * Lambda 會實現 `Function<N>` 接口,N 的大小由參數決定,接收參數越多則 N 越大 * **`inline` 內聯函數** 則 ++不需實現++ Function 接口! ::: ### Kotlin 調用 Java 函數式 * Kotlin 中調用 Java 也可以使用函數式 (Lambda),但須有一些條件限制 (其實也就是 [**Java 中實現 Lambda 的方法**](https://hackmd.io/gV5_yfM-RHuVX257ur-P6A?view)),**==Java 抽象接口只能有單一個實現方法==** ```java= // Runnable 原碼 @FunctionalInterface public interface Runnable { public abstract void run(); } // Java 使用匿名內部類調用 public class JavaThread { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("Hello Java Thread"); } }).start(); } } ``` 從上面可以看出來 **`Runnable` 這個介面內部只有一個方法(`run`)**,符合 Kotlin 調用函數式 API 的規定 :::warning * **匿名內部類** 匿名內部類必須使用關鍵字 `object`,因為 Kotlin 捨棄了 `new` 關鍵字,所以在創建匿名內部類時要使用 `object` ::: ```kotlin= fun main() { javaThread1() javaThread2() javaThread3() javaThread4() } /** * 匿名內部類必須使用關鍵字 "object" * 因為 Kotlin 捨棄了 new 關鍵字,所以在創建匿名內部類時要使用 object */ fun javaThread1() { Thread(object : Runnable { override fun run() { println("Kotlin use Runnable - 1") } }).start() } /** * object 可以提醒未實現方法,但 Runnable 就只有一個實現方法而已 * 所以可以省略 object & run 方法 (因為 Kotlin 可以自動推導知道 */ fun javaThread2() { Thread(Runnable { println("Kotlin use Runnable - 2") }).start() } /** * 如果 Java 方法的參數列表中只有一個 Java 抽象接口,則 Kotlin 可以自動推倒 * 所以可以省略 Runnable */ fun javaThread3() { Thread({ println("Kotlin use Runnable - 3") }).start() } /** * 黨參數 Lambda 表達式為最後一個參數時可以省略 (),只留下 {} 大括號 */ fun javaThread4() { Thread { println("Kotlin use Runnable - 4") }.start() } ``` **--實作結果--** > ![](https://i.imgur.com/dUSQoSz.png) ## Array 數組 ```kotlin= /** * arrayOf 是內建函數 */ fun main() { val array = arrayOf(1, 3, 5, 7, 9) for (i in array) { println("array: $i") } // 原型: public inline constructor(size: Int, init: (Int) -> T) val lambda = {i : Int -> i + 10} // i 從 0 開始 val array2 = Array(5, lambda) for (i in array2) { println("lambda i : $i") } /** * 省略方式 * 1. 單一參數可以使用 it * 2. lambda 最為最後一個參數可以一至 (外) */ val array3 = Array(5) {it + 10} // i 從 0 開始 for (i in array3) { println("lambda i --- 2 : $i") } } ``` **-實作結果--** > ![](https://i.imgur.com/Z1gOUki.png) ### Collections 集合 * 在 Java 中的集合有 List、Set、Map,它們的實現如下 (列舉出常用的) | 集合 | 實現 | | -------- | -------- | | List | ArrayList、LinkedList | | Set | HashSet | | Map | HashMap | * Kotlin 對於集合的創建有簡化的內置函數 | 集合內置函數 | 說明特性 | | -------- | -------- | | listOf | 初始化時就必須決定集合內容,**並且該集合 ++內容不可更改的++** | | mutableListOf | mutable 關鍵字如同 C++ 的關鍵字,說明該 **集合內容可以改變** | | setOf | 如同 list 集合,同樣是不可更改的集合 | | mutableSetOf | 可以更改的 Set 集合 | | mapOf | 不可改變的 map 集合,**Kotlin 不建議使用 put、get,改用指標下定的方式** | | mutableMapOf | 可更改的 Map 集合,並使用 infix 函數 | ```kotlin= fun main() { customArray() kotlinList() kotlinListChange() kotlinSet() kotlinSetChange() customMap() kotlinIndex() kotlinMap() kotlinMapChange() forListWithIndex() } /** * Kotlin 可以實現如同 Java 的 List */ fun customArray() { val list = ArrayList<Int>() list.add(123) list.add(456) list.add(789) println("custom list: $list") } /** * Kotlin 有專屬的 "內置" 函數 listOf (不可更改 !!! * 用來簡化 list 的初始化創建 */ fun kotlinList() { val list = listOf<String>("Apple, Banana, Car") // list[0] = "Bpple"; Error: listOf 內容不可更改,內部元素都是 val // list.add(); 也不可以新增元素 println("listOf: $list") } /** * mutableListOf 是一個可以更改內容的集合 */ fun kotlinListChange() { val list = mutableListOf<String>("Hello", "World") println("mutableListOf before change: $list") list[1] = "Kotlin" // Okay: mutableListOf 內部元素可以更改 list.add("Go to Lean~") // 也可以新增內容 println("mutableListOf after change: $list") } fun kotlinSet() { val set = setOf<String>("AAA", "BBB", "CCC") for ( i in set) { println("setOf element: $i") } } fun kotlinSetChange() { val set = mutableSetOf("DDD") set.add("EEE") set.add("FFF") for ( i in set) { println("mutableSetOf element: $i") } } fun customMap() { val map = HashMap<String, Long>() map.put("Alien", 9527L) map.put("Pan", 3344L) map.put("Kyle", 5566L) println("custom Map: $map") // for (i in map) { // 遍歷 Okay // println("key: ${i.key}, value: ${i.value}") // } } fun kotlinIndex() { val map = HashMap<String, Long>() map["Alien"] = 9527L map["Pan"] = 3344L map["Kyle"] = 5566L println("custom Map use index: $map") } /** * to 是一個 index 函數 */ fun kotlinMap() { val map = mapOf<Char, Int>('A' to 1, 'B' to 2, 'C' to 3) // map['A'] = 10 Error 不可賦值 println("mapOf: $map") } fun kotlinMapChange() { val map = mutableMapOf<Char, Int>('C' to 4) map['D'] = 5 map['E'] = 6 println("mutableMapOf: $map") } /** * 要取得 index 必須使用 集合.indices */ fun forListWithIndex() { val list = listOf<Char>('A', 'B', 'C') list.forEach { a -> println(a) } for (index in list.indices) { println("index: $index, value: ${list[index]}") } } ``` **--實作結果--** > ![](https://i.imgur.com/lOXhkfq.png) ### 集合函數式 API:map、filter、any、all * 以下會提及幾個常用的集合 API,其接收參數就是使用 Lambda 1. **集合的 `map` 函數**:它會將集合中的每一個元素都套用傳入的 Lambda,並產生一個新的值,最終生成新的集合 ```kotlin= fun main() { val list = listOf(100, 200, 300, 400, 500) collectionMap(list) } fun collectionMap(list:List<Int>) { // map 會產生新集合 val newList = list.map { it/100 } for (i in newList) { println("list2 : $i") } } ``` > ![](https://i.imgur.com/0u0xHdz.png) 2. **集合的 `filter` 函數**:同樣 Lambda 會對集合內的每一個元素作用,最後產生一個符合條件的新集合 ```kotlin= fun main() { val list = listOf(100, 200, 300, 400, 500) collectionMap(list) collectionFilter(list) } fun collectionFilter(list: List<Int>) { val newList = list.filter { it in 300..400 } // 區間 for (i in newList) { println("list filter : $i") } } ``` > ![](https://i.imgur.com/W2s1ZNX.png) 3. **集合的 `any` 函數**:判斷集合內任意的元素是否有符合條件 ```kotlin= fun main() { val list = listOf(100, 200, 300, 400, 500) collectionMap(list) collectionFilter(list) collectionAny(list) } fun collectionAny(list: List<Int>) { val isPass = list.any { it > 200} println("list any: $isPass") } ``` > ![](https://i.imgur.com/KUNEu7T.png) 4. **集合的 `all` 函數**,判斷集合內是否每一個元素都符合條件 ```kotlin= fun main() { val list = listOf(100, 200, 300, 400, 500) collectionMap(list) collectionFilter(list) collectionAny(list) collectionAll(list) } fun collectionAll(list: List<Int>) { val isPass = list.all { it > 200} println("list all: $isPass") } ``` > ![](https://i.imgur.com/qLUoU4q.png) ## 更多的 Kotlin 語言相關文章 在這裡,我們提供了一系列豐富且深入的 Kotlin 語言相關文章,涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容! ### Kotlin 語言基礎 * **Kotlin 語言基礎**:想要建立堅實的 Kotlin 基礎?以下這些文章將帶你深入探索 Kotlin 的關鍵基礎和概念,幫你打造更堅固的 Kotlin 語言基礎 :::info * [**Kotlin 函數、類、屬性 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/) * [**深入探究 Kotlin 與 Java 泛型:擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/) * [**深入 Kotlin 函數特性:Inline、擴展、標準函數全解析 | 提升程式碼效能與可讀性**](https://devtechascendancy.com/kotlin_inline_extensions_standards-func/) * [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/) ::: ### Kotlin 特性、特點 * **Kotlin 特性、特點**:探索 Kotlin 的獨特特性和功能,加深對 Kotlin 語言的理解,並增強對於語言特性的應用 :::warning * [**Kotlin 代理與懶加載機制:使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/) * [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/) * [**深入理解 Kotlin:智能推斷與 Contact 規則**](https://devtechascendancy.com/kotlin-smart-inference-contract-rules-guide/) ::: ### Kotlin 進階:協程、響應式、異步 * **Kotlin 進階:協程、響應式、異步**:若想深入學習 Kotlin 的進階主題,包括協程應用、Channel 使用、以及 Flow 的探索,請查看以下文章 :::danger * [**應用 Kotlin 協程:對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/) * [**Kotlin Channel 使用介紹 | Select、Actor | 生產者消費者**](https://devtechascendancy.com/kotlin-channel_select_actor_cs/) * [**探索 Kotlin Flow:基本使用、RxJava 對比、背壓機制 | Flow 細節**](https://devtechascendancy.com/kotlin-flow-usage_compare-rx_backpressure/) ::: ## Appendix & FAQ :::info ::: ###### tags: `Kotlin`