kotlin standard library 大量使用了 lambda 語法,最常見的就是用在 collections,另外也提供如何從 java 呼叫 kotlin lambda 的方式。最後說明一種特別的 lambda with receivers。
lambda expression
直到 Java 8 才提供了 lambda 語法。以下說明 lambda 的重要性
block of code as method parameters
如果要實作當某個事件發生時,執行一個 handler 的 code,以 java 來說可以用 anonymous inner class 實作。如果用 functional programming 的方式,可以將 function 當作 value,也就是將 function 當作參數傳遞給另一個 function,也就是把一小段 code 當作method 參數。
listener 實作 OnclickListener 介面,覆寫 onClick
/* Java */
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
/* actions on click */
}
});
換成functional progeamming 的方式
button.setOnClickListener { /* actions on click */ }
lambda and collections
假設有個 list of data class: Person,需要找到裡面年紀最大的人,直覺會以 function 實作
data class Person(val name: String, val age: Int)
fun findTheOldest(people: List<Person>) {
var maxAge = 0
var theOldest: Person? = null
for (person in people) {
if (person.age > maxAge) {
maxAge = person.age
theOldest = person
}
}
println(theOldest)
}
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
findTheOldest(people)
}
kotlin 有提供這個 libray function: maxBy,maxBy 可用在任何一種 collection,後面的 {}
是 lambda implementation
>>> val people = listOf(Person("Alice", 29), Person("Bob", 31))
>>> println(people.maxBy { it.age })
Person(name=Bob, age=31)
maxBy 也可以直接指定 member reference
people.maxBy(Person::age)
lambda expression 語法
lambda 語法是在 {}
裡面,前面是參數,後面是 body
val sum = { x: Int, y: Int -> x + y }
println(sum(1, 2))
// 3
也可以直接呼叫 lambda
>>> { println(42) }()
42
但最好用 run 封裝起來,code 會比較容易閱讀
>>> run { println(42) }
42
剛剛的 people.maxBy { it.age }
其實原本應該寫成,使用 p:Person
這個參數,運算 p.age
people.maxBy({ p: Person -> p.age })
可以將 ()
省略
people.maxBy { p: Person -> p.age }
將 p 的資料型別省略
people.maxBy { p -> p.age }
將 p 改成 lambda 的預設參數名稱 it
people.maxBy { it.age }
joinToString 也有在 kotlin 標準函式庫裡面,其中 transform 就是需要提供一個 lambda function
>>> val people = listOf(Person("Alice", 29), Person("Bob", 31))
>>> val names = people.joinToString(separator = " ", transform = { p: Person -> p.name } )
>>> println(names)
Alice Bob
也可以簡化寫成這樣
people.joinToString(" ") { p: Person -> p.name }
lambda 語法裡面,也可以寫超過一個 expression 或 statement
val sum = { x: Int, y: Int ->
println("Computing the sum of $x and $y")
x + y
}
accessing variables
如果在 method 裡面宣告 anonymous inner class 時,可使用 method 裡面的 local 變數以及 parameters,lambda 也是一樣。
messages.forEach
裡面的 lambda function 使用了外部 method 的參數 $prefix
fun printMessagesWithPrefix(messages: Collection<String>, prefix: String) {
messages.forEach {
println("$prefix $it")
}
}
fun main(args: Array<String>) {
val errors = listOf("403 Forbidden", "404 Not Found")
printMessagesWithPrefix(errors, "Error:")
}
kotlin 跟 java 最大的差異是 kotlin 沒有限制只能使用 final variables,也可以在 lambda function 裡面修改變數的值。
fun printProblemCounts(responses: Collection<String>) {
var clientErrors = 0
var serverErrors = 0
responses.forEach {
if (it.startsWith("4")) {
clientErrors++
} else if (it.startsWith("5")) {
serverErrors++
}
}
println("$clientErrors client errors, $serverErrors server errors")
}
fun main(args: Array<String>) {
val responses = listOf("200 OK", "418 I'm a teapot",
"500 Internal Server Error")
printProblemCounts(responses)
}
member references
如果 lambda 要傳送一個已經定義好的 function 時,要使用 ::
operator,將 function 轉換為 value,這種語法稱為 member reference,前面是 class,後面是 method 或是 property
val getAge = Person::age
等同
val getAge = { person: Person -> person.age }
如果是 top-level function,就不用寫 class name
fun salute() = println("Salute!")
fun main(args: Array<String>) {
run(::salute)
}
將 lambda function 轉送給 sendEmail function
val action = { person: Person, message: String ->
sendEmail(person, message)
}
直接使用 member reference
val nextAction = ::sendEmail
也可以將 member reference 套用在 class constructor
>>> data class Person(val name: String, val age: Int)
>>> val createPerson = ::Person
>>> val p = createPerson("Alice", 29)
>>> println(p)
Person("Alice", 29)
也可以使用 extension function
fun Person.isAdult() = age >= 21
val predicate = Person::isAdult
collection 使用的 functional APIs
filter and map
取得偶數的元素,it 是 lambda 的預設變數名稱
fun main(args: Array<String>) {
val list = listOf(1, 2, 3, 4)
println(list.filter { it % 2 == 0 })
}
取得年齡超過30歲的 Person
data class Person(val name: String, val age: Int)
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.filter { it.age > 30 })
}
取得每一個元素的平方 list
fun main(args: Array<String>) {
val list = listOf(1, 2, 3, 4)
println(list.map { it * it })
}
取得所有人的名字 list
data class Person(val name: String, val age: Int)
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 29), Person("Bob", 31))
println(people.map { it.name })
}
也可以寫成people.map(Person::name)
可以將 filter 與 map 連在一起 people.filter { it.age > 30 }.map(Person::name)
找到年齡最大的 personpeople.filter { it.age == people.maxBy(Person::age).age }
但因為 people.maxBy(Person::age).age
重複做了很多次,所以將這兩個分開做,效能會比較好
val maxAge = people.maxBy(Person::age).age
people.filter { it.age == maxAge }
使用 filter 及 transformation functions to maps
fun main(args: Array<String>) {
val numbers = mapOf(0 to "zero", 1 to "one")
println(numbers.mapValues { it.value.toUpperCase() })
}
//{0=ZERO, 1=ONE}
all, any, count, find
檢查是不是所有元素都符合某個條件
data class Person(val name: String, val age: Int)
val canBeInClub27 = { p: Person -> p.age <= 27 }
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 27), Person("Bob", 31))
println( people.all(canBeInClub27) )
}
檢查是否至少有一個元素符合某個條件
println(people.any(canBeInClub27))
!all
跟 any
的意義是相反的,可以互換
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
println(!list.all { it == 3 })
println(list.any { it != 3 })
}
count
計算數量
al people = listOf(Person("Alice", 27), Person("Bob", 31))
println(people.count(canBeInClub27))
用 find 找到一個符合條件的元素
data class Person(val name: String, val age: Int)
val canBeInClub27 = { p: Person -> p.age <= 27 }
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 27), Person("Bob", 31))
println(people.find(canBeInClub27))
}
groupBy: 將 list 轉換為 map of groups
將 list 以 age 分組
data class Person(val name: String, val age: Int)
fun main(args: Array<String>) {
val people = listOf(Person("Alice", 31),
Person("Bob", 29), Person("Carol", 31))
println(people.groupBy { it.age })
}
執行結果為 Map<Int, List<Person>>
{31=[Person(name=Alice, age=31), Person(name=Carol, age=31)], 29=[Person(name=Bob, age=29)]}
以 string 的第一個 character 分組
fun main(args: Array<String>) {
val list = listOf("a", "ab", "b")
println(list.groupBy(String::first))
}
執行結果
{a=[a, ab], b=[b]}
flatMap, flatten
flatMap 做兩件事:根據參數,轉換 (maps) 每一個 element 為 collection,合併 (flattens) 數個 lists 為一個
fun main(args: Array<String>) {
val strings = listOf("abc", "def")
println(strings.flatMap { it.toList() })
}
執行結果
[a, b, c, d, e, f]
set of all authors
class Book(val title: String, val authors: List<String>)
fun main(args: Array<String>) {
val books = listOf(Book("Thursday Next", listOf("Jasper Fforde")),
Book("Mort", listOf("Terry Pratchett")),
Book("Good Omens", listOf("Terry Pratchett",
"Neil Gaiman")))
println(books.flatMap { it.authors }.toSet())
}
執行結果
[Jasper Fforde, Terry Pratchett, Neil Gaiman]
lazy collection operations: sequences
sequence 不同於 collection,在使用到該物件時才會進行運算,而不是在定義時,就馬上進行運算,也不會產生 intermediate temporary objects
asSequence 是讓 collection 轉成 sequence,toList 則是讓 sequence 轉回 collection
fun main(args: Array<String>) {
val seq = listOf(1, 2, 3, 4).asSequence()
.map { print("map($it) "); it * it }
.filter { print("filter($it) "); it % 2 == 0 }
println( "after seq")
val list = seq.toList()
println( )
println( list )
}
執行結果
after seq
map(1) filter(1) map(2) filter(4) map(3) filter(9) map(4) filter(16)
[4, 16]
在 sequence 的 operations 中,map 及 filter 是 intermediate operations,toList 是 terminal operation,一直到 terminal operation 才會進行運算
sequence.map{...}.filter{...}.toList()
除了 asSequence() 還可以用 generateSequence() 產生 sequence,一直到呼叫 sum 的時候,才會進行運算
fun main(args: Array<String>) {
val naturalNumbers = generateSequence(0) { it + 1 }
val numbersTo100 = naturalNumbers.takeWhile { it <= 100 }
println(numbersTo100.sum())
}
另一個常用的是 sequence of parents,以下是檢查 file 是否在某一個隱藏的目錄中,所以要產生 sequence of parent directories,然後檢查是否有任一個為 hidden
import java.io.File
fun File.isInsideHiddenDirectory() =
generateSequence(this) { it.parentFile }.any { it.isHidden }
fun main(args: Array<String>) {
val file = File("/Users/svtk/.HiddenDir/a.txt")
println(file.isInsideHiddenDirectory())
}
using Java functional interfaces
Kotlin lambdas 可跟 Java APIs 一起使用
在 java 的 setOnClickListener
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
...
}
}
在 kotlin
button.setOnClickListener { view -> ... }
其中 OnClickListener 稱為 funcational interface 或是 SAM(single abstract method) interfaces
public interface OnClickListener {
void onClick(View v);
}
passing a lambda as a parameter to a java method
將 lambda 傳到需要 functional interface 的 java method
/* Java */
void postponeComputation(int delay, Runnable computation);
在kotlin 可這樣呼叫,compiler 會自動產生一個 instance of Runnable,也就是 instance of an anonymous class implements Runnable
postponeComputation(1000) { println(42) }
compiler 會用 run method 產生 instance of anonymous class that implements Runnable
postponeComputation(1000, object: Runnable {
override fun run() {
println(42)
}
})
每一個呼叫 handleComputation 都會產生一個新的 Runnable instance 儲存 id 欄位
fun handleComputation(id: String) {
postponeComputation(1000) {
println(id)
}
}
SAM constructors: explicit conversion of lambdas to functional interfaces
SAM constructor 是 compiler 產生的 function,可轉換 lambda 為 instance of functional interface
我們不能直接 return lambda,但可包裝在 SAM constructor 裡面
fun createAllDoneRunnable(): Runnable {
return Runnable { println("All done!") }
}
fun main(args: Array<String>) {
createAllDoneRunnable().run()
}
例如 Android 的 listener,可以產生出來給多個 button 使用
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
lambda with receivers: with and apply
以下這是 kotlin 的 lambda 的功能,不能在 java 使用。
可以在 lambda 呼叫 methods of a different object,不需要額外的 qualifiers
with
以下是列印英文字母的範例
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nNow I know the alphabet!")
return result.toString()
}
fun main(args: Array<String>) {
println(alphabet())
}
用 with 改寫
fun alphabet(): String {
val stringBuilder = StringBuilder()
// 指定 receiver value
return with(stringBuilder) {
for (letter in 'A'..'Z') {
// 以 this 呼叫 receiver value 的 method
this.append(letter)
}
// 呼叫 method,但省略 this
append("\nNow I know the alphabet!")
// 由 lambda 回傳 value
this.toString()
}
}
fun main(args: Array<String>) {
println(alphabet())
}
省略 this
fun alphabet() = with(StringBuilder()) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
fun main(args: Array<String>) {
println(alphabet())
}
apply
with 的 value 會回傳 lambda code 的最後一個 expression,但有時需要 return receiver object,而不是 lambda 運算結果,這時要改用 apply
apply 用起來就像是 with,差別是 apply 會回傳傳入作為參數的 object
fun alphabet() = StringBuilder().apply {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}.toString()
fun main(args: Array<String>) {
println(alphabet())
}
也可以改用 buildString,buildString 會處理 StringBuffer,也會呼叫 toString,buildString 是 lambda with a receiver,且 receiver 是 StringBuilder
fun alphabet() = buildString {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
}
fun main(args: Array<String>) {
println(alphabet())
}
沒有留言:
張貼留言