Kotlin高階函式

張益裕 Michael Chang

  • 精誠資訊/恆逸教育訓練中心-資深講師
  • 技術分類:程式設計

 

 

Java 8開始支援Functional Programming,除了原有的物件導向程式設計語言,也可以使用Lambda expression撰寫程式。不過Java並沒有完整的支援Functional Programming,Lambda expression只能使用在方法或建構式的參數傳遞。

Java 8定義只有一個抽象方法的interface為functional interface,例如下列的宣告:

package java.util.function;
import java.util.Objects;

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    ...
}    

方法或建構式如果接收functional interface型態的參數,呼叫這個方法的時候就可以使用lambda expression提供參數,例如下列在java.util.stream.Stream中的宣告:

Stream<T> filter(Predicate<? super T> predicate)   

Kotlin程式設計語言同時支援物件導向與Functional Programming,Kotlin的function為first-class,這表示Kotlin的function可以儲存為變數,使用在參數的傳遞與回傳給其它higher-order function。

一般的函式可以根據需求定義一個回傳值型態,回傳值可以是任何物件或基本資料型態。高階函式允許函式的回傳值為函式型態,這樣的做法可以讓應用程式的設計更加靈活。為了說明高階函式中的函式回傳型態,下列宣告的類別與列舉型態使用在後續的範例:

// 客戶等級與對應的折扣
enum class Level(val discount: Double) {
    NORMAL(0.9), VIP(0.8), GOLDEN(0.7)
}

// 訂單類別,包含訂單數量
data class Order(var amount: Int)    

應用程式需要根據訂單數量與客戶的等級計算折扣,這樣的組合可以使用下列的函式:

// 計算折扣的函式,接收訂單物件參數
// 回傳計算折扣的函式,參數為訂單物件,回傳值為Double,依照訂單數量計算的折扣
fun getDiscount(level: Level): (Order) -> Double =
        { order ->
            val amountDiscount =
                when (order.amount) {
                    in 1..10 -> 0.1
                    in 11..20 -> 0.2
                    in 21..30 -> 0.3
                    else -> 0.4
                }
            // 客戶折扣再減去數量折扣
            level.discount - amountDiscount
        }      

設計好相關的函式後就可以靈活的應用:

fun main(args: Array<String>) {
    // 取得計算折扣的函式變數
    val discount = getDiscount(Level.VIP)
    // 呼叫函式取得訂單的折扣
    println( discount( Order(16) ) )
}         

為了對高階函式應用能有更進一步認識,下列宣告的類別與列舉型態使用在後續的範例:

// 會員類別,包含名稱與編號
data class Member(val firstName: String,
                    val lastName: String,
                    val id: String)

// 會員名稱種類
enum class MemberName {FIRST, LAST, ALL}            

應用程式需要提供會員名稱搜尋與過濾的功能,包含first name、last name或全名,為了應付這樣的需求,使用回傳的函式提供應用程式不同功能的需求,是一種最靈活的設計:

// 會員名稱搜尋函式,接收搜尋內容與種類參數
// 回傳會員過濾條件函式,參數為會員物件,回傳值為Boolean,依照名稱種類執行判斷
fun getMemberPredicate(prefix: String,
                        pn: MemberName): (Member) -> Boolean =
    when (pn) {
        MemberName.FIRST ->
            { p: Member -> p.firstName.startsWith(prefix) }
        MemberName.LAST ->
            { p: Member -> p.lastName.startsWith(prefix) }
        MemberName.ALL ->
            { p: Member -> p.firstName.startsWith(prefix) ||
                    p.lastName.startsWith(prefix)}
    }                

設計好這個函式以後,就可以很容易在資料結構中執行搜尋與過濾會員資料的工作:

fun main(args: Array<String>) {
    val people = listOf(Member("Simon", "Johnson", "101"),
                        Member("Mary", "Johnson", "102"),
                        Member("Sam", "Johnson", "101"))

    println("\n===== First name starts with Si")
    val predicate01 = getMemberPredicate("Si", MemberName.FIRST)
    people.filter(predicate01).forEach { println(it) }

    println("\n===== First name or last name starts with Jo")
    val predicate02 = getMemberPredicate("Jo", MemberName.ALL)
    people.filter(predicate02).forEach { println(it) }
}                    

您可在下列課程中了解更多技巧喔!