Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API

Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API

Overview of Content

本文將全面探討 Kotlin Lambda 編程的相關主題,首先介紹編程基礎,隨後深入解析 Kotlin Lambda 的概念和使用方法。透過逐步引導,我們將探討 Lambda 在 Kotlin 中的具體應用,並深入研究其在 ByteCode 層面的實現。

此外,我們將探討 Kotlin 如何與 Java 函數式相互操作,為讀者提供跨語言編程的實際示例。另一方面,我們也將深入研究數組(Array)和集合(Collections),並介紹 Kotlin 強大的集合函數式 API,包括 mapfilteranyall 等功能,以協助讀者更深入理解 Kotlin 編程的精髓。

以下參考,第一行代碼 (第三版), Kotlin 進階實戰

如有引用參考本文章請詳註出處,感謝 🙂


Kotlin Lambda 編程介紹

Kotlin 從第一版開始就支持 Lambda 編程,並且 Lambda 在 Kotlin 的功能極為強大,這裡會先以基礎為例,還有其他高階函數、DSL...等等


Lambda & Kotlin: Step by Setp

● 這裡的重點是學習 函數式 API 的語法結構,就是 Lambda 表達式在 Kotlin 中的運用,Lambda 可以想做把函式當作參數傳入

先來看看 Lambda 表達式在 Kotlin 中的語法


/**
 * 1. 最外面大括號
 * 2. 參數列表的尾端使用 `->` 符號
 * 3. 並且 Lambda 最後一行為返回的數值
 */

{參數名1: 參數類型, 參數名2: 參數類型 -> 函數體}

以下在集合中求取最大長度的字串,第一個是傳統的方式,第二個是 Lambda 的使用,之後皆是推導 (不然還真的看不懂)


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")
}

● 以下整理重點步驟

A. Lambda 原型


list.maxBy{(i: String -> i.length)}

B. 當最後一個參數為 Lambda 時可以將 Lambda 移到 () 外面


list.maxBy() {i: String -> i.length}

C. 若 Lambda 為唯一一個參數時可以移除小括號 ()


list.maxBy{i: String -> i.length}

D. 由於 Kotlin 有自動推倒機制,所以可以省略參數的類型


list.maxBy{i -> i.length}

E. 若「只有一個參數」則可以不用聲明參數,可以使用關鍵字 it


list.maxBy{it.length}

--實作結果--

● 返回一個函數:一般來講程式語言只能返回基礎類型,但是函數式編程的特點就在於,可以將「函數作為值」返回


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)}")

}

Lambda 同時也可以作為一個返回值返回

● Lambda 中「未」使用到的參數建議使用 _ 取代其參數名稱,這可以增加程式的可讀性!

解析 Kotlin Lambda:ByteCode

● 現在換個角度來看看 Lambda 表達式:先撰寫 Kotlin 一個帶有 Lambda 函數式的程式,再將其編譯,最後查看 .class 文件


fun main() {

    val sum = {
        x: Int, y : Int -> x + y
    }

    println(sum(1, 2))

}

● 透過 IDE 提供的 kotlin Bytecode 工具查看該文件的 .class 檔案

image

A. 呼叫 sum Lambda 函數:可以看到它調用了 Function2#invoke 方法(Function2 代表帶兩個參數的函數)

B. 而 sum 函數則實作了 Function2 介面

● Lambda 會實現 Function<N> 接口,N 的大小由參數決定,接收參數越多則 N 越大

inline 內聯函數不需實現 Function 接口!

Kotlin 調用 Java 函數式

● Kotlin 中調用 Java 也可以使用函數式 (Lambda),但須有一些條件限制 (其實也就是 Java 中實現 Lambda 的方法),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 的規定

匿名內部類

匿名內部類必須使用關鍵字 object,因為 Kotlin 捨棄了 new 關鍵字,所以在創建匿名內部類時要使用 object


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()
}

--實作結果--

Array 數組


/**
 * 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")
    }
}

-實作結果--

Collections 集合

● 在 Java 中的集合有 List、Set、Map,它們的實現如下 (列舉出常用的)

集合實現
ListArrayList、LinkedList
SetHashSet
MapHashMap

● Kotlin 對於集合的創建有簡化的內置函數

集合內置函數說明特性
listOf初始化時就必須決定集合內容,並且該集合 內容不可更改的
mutableListOfmutable 關鍵字如同 C++ 的關鍵字,說明該 集合內容可以改變
setOf如同 list 集合,同樣是不可更改的集合
mutableSetOf可以更改的 Set 集合
mapOf不可改變的 map 集合,Kotlin 不建議使用 put、get,改用指標下定的方式
mutableMapOf可更改的 Map 集合,並使用 infix 函數

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]}")
    }
}

--實作結果--

集合函數式 API:map、filter、any、all

● 以下會提及幾個常用的集合 API,其接收參數就是使用 Lambda

A. 集合的 map 函數:它會將集合中的每一個元素都套用傳入的 Lambda,並產生一個新的值,最終生成新的集合


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")
    }
}

B. 集合的 filter 函數:同樣 Lambda 會對集合內的每一個元素作用,最後產生一個符合條件的新集合


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")
    }
}

C. 集合的 any 函數:判斷集合內任意的元素是否有符合條件


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")
}

D. 集合的 all 函數,判斷集合內是否每一個元素都符合條件


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")
}

更多的 Kotlin 語言相關文章

在這裡,我們提供了一系列豐富且深入的 Kotlin 語言相關文章,涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容!

Kotlin 特性、特點

Kotlin 特性、特點:探索 Kotlin 的獨特特性和功能,加深對 Kotlin 語言的理解,並增強對於語言特性的應用

Kotlin 進階:協程、響應式、異步

Kotlin 進階:協程、響應式、異步:若想深入學習 Kotlin 的進階主題,包括協程應用、Channel 使用、以及 Flow 的探索,請查看以下文章

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響