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